hello world demo看完后其实基本的写法就会了。

但是omi中的组件是神马鬼?其实我也不知道组件是啥。

百度百科是这么说的: 是对数据和方法的简单封装。es6中,一个类其实也可以做到对方法和数据的封装。然后new出来的实例共享原型上的方法,至于属性最好不要共享啦,

如果需要共享,自己写静态属性,或者Object.assign到原型上去。这里有点扯远了。

我的理解是一个组件就是一个类,至于组件嵌套,其实就是父类和子类,无非就是挂载到对应的属性下

(父类会主动帮我们自动的new 子类(被嵌套的组件)的实例并且添加些相应的属性,然后和父组件中render中的html合并一下)。

接下来看看一个组件的demo。

老规矩:先上demo代码, 然后提出问题, 之后解答问题, 最后源码说明。

        // 组件嵌套抽出List
class List extends Omi.Component {
constructor(data) {
super(data);
} render() {
return `
<ul>
{{#items}}
<li>
{{.}}
</li>
{{/items}}
</ul>
`;
}
}; Omi.makeHTML('List2', List); // 使用Omi.makeHTML把List类制作成可以声明式的标签List2,在render方法中就能直接使用该标签 class Todo extends Omi.Component {
constructor(data) {
super(data);
this.data.length = this.data.items.length; // 给data添加个length属性
this.listData = {items: this.data.items}; // listData属性名和下面的data="listData"中的listData对应
} style() {
return `
h3 {
color: red;
}
button {
color: green;
}
`;
} handleChange(target, evt) {
this.data.text = target.value;
console.log(this.data.text);
} add(evt) {
evt.preventDefault();
this.instance_list.data.items.push(this.data.text); // this.instance_list这个其实就是下面name="instance_list"的instance_list(他其实就是List类的实例)
this.data.length = this.listData.items.length; // 跟新属性
this.data.text = '';
this.update();
console.log(this.data);
console.log(this.instance_list.data); // this.instance_list中的data属性其实是一级浅拷贝this.listData这个的
console.log(this.listData);
} render() {
return `
<div>
<h3>TODO</h3>
<List2 name="instance_list" data="listData"></List2> <!--name 对应List2标签对应的List类的实例, data的listData属性其实浅拷贝到instance_list实例的data上-->
<form>
<input type="text" onchange="handleChange(this, event)" value="{{text}}" />
<button onclick="add(event)">add #{{length}}</button>
</form>
</div>
`;
}
}; var todo = new Todo({
items: [1, 2],
text: ''
});
Omi.render(todo, '#app');
console.log(todo.instance_list);

先看看omi中文文档的说明:

额,因为demo源码是我自己敲的,有稍微变化,所以说明就挑重要的说明,

通过makeHTML方法把组件制作成可以在render中使用的标签。使用Omi.makeHTML('List2', List);即可
 在父组件上定义listData属性用来传递给子组件。
 在render方法中使用List2组件。
      其中name方法可以让你在代码里通过this快速方法到该组件的实例。
      data="listData"可以让你把this.listData传递给子组件。
  需要注意的是,父组件的this.listData会被通过Object.assign浅拷贝到子组件。
  这样做的目的主要是希望以后DOM的变更都尽量修改子组件自身的data,然后再调用其update方法,而不是去更改父组件的listData。

文档地址:https://alloyteam.github.io/omi/website/docs-cn.html#

接下来说说这个demo的疑问和疑问的说明:

疑问1:
Omi.makeHTML('List2', List);这个语句是干啥的,参数类型分别是啥?

答: 这是Omi对象的一个静态方法,作用是把类制作成可以声明式的标签。

这么说似乎有点难懂。
       简单的说就是一个名字对应一个构造函数,也就是键值对。是存放在Omi.componetConstructor这个对象上的。
       并且也把标签名存到Omi.customTags这个数组中。

源码感受:

    Omi.makeHTML= function(name, ctor) {    // name: 组件标签名, ctor: 构造函数也就是类啦
Omi.componetConstructor[name] = ctor; // 把标签名对应类放到componetConstructor对象中去
Omi.customTags.push(name); // 自定义标签
};

那这么做的目的就是就是当我们使用omi.render的时候,他会自动帮我们new List实例然后合并父组件中的html。(这里当然是循环遍历每个孩子标签啦)。继续往下看。

疑问2:
我的List2标签制作好了,该怎么用到其他组件中去呢?

标签是使用单标签还是双标签呢?

答:可以在任意一个组件类的render方法中使用制作好的List2标签。可以如下使用:

<List2 name="instance_list" data="listData"></List2>    双标签
//或者
<List2 name="instance_list" data="listData" /> 单标签

这里面的 name="instance_list" data="listData" 又是神马鬼啊,其实是这样的

属性name对应的值instance_list其实就是 new List()的实例

属性data对应的值listData就是被一级浅拷贝到instance_list实例上的data上了。

那么的话,我操作数据的话,可以操作instance_list上的data数据然后instance_list.update()即可(原作者推荐),其实我们也可以在父类上操作listData属性然后this.update(),数据就更新了。

哇,这么牛逼,怎么做到的呢?继续往下看

疑问3:
上面的<List2 name="instance_list" data="listData"></List2>这个标签里面的属性是不是涉及到组件通讯了啊?

答:是的,组件通讯有4种,加上一个终极通讯模式(上帝模式),后续会讲解的。

本demo的通讯其实通过List2标签上的data属性值listData来实现通讯的。

疑问4:

这些都写好了,那怎么把<List2 name="instance_list" data="listData"></List2> 这个标签转换成如下这个标签呢?

答:恩,原理其实很简单撒,就是先把4种通讯方式中的数据合并,然后实例化 List2标签对应的的类,然后得到实例,之后生成局部css和html,然后替换这个标签,在之后把内置事件

对应起类中的方法,然后插入到指定的dom中去就完了(组件嵌套组件然后各种嵌套,就变成了各种递归)。说起来很简单,真要是实现起来不容易啊,看看作者是怎么实现的。

源码感受:

从用户的代码中可以看到,omi.render方法是主角或者是入口吧。

    Omi.render = function(component , renderTo , incrementOrOption){    // 实例, 渲染到的dom, xx
component.renderTo = typeof renderTo === "string" ? document.querySelector(renderTo) : renderTo; // 实例的renderTo属性
if (typeof incrementOrOption === 'boolean') {
component._omi_increment = incrementOrOption; // 实例的_omi_increment 属性(老版)
} else if (incrementOrOption) { // 新增
component._omi_increment = incrementOrOption.increment;
component.$store = incrementOrOption.store;
if (component.$store) {
component.$store.instances.push(component);
};
component._omi_autoStoreToData = incrementOrOption.autoStoreToData;
};
component.install(); // Component类的install方法(被实例继承了)
component._render(true); // Component类的_render方法(被实例继承了)
component._childrenInstalled(component); // 给每个实例的孩子执行installed方法
component.installed(); // Component类的installed方法(被实例继承了)
return component; // 返回实例
};

这里面对于这个demo最主要的是component._render(true);这个方法,那我们进去看一看

    _render(isFirst) {
if (this._omi_removed ) { // 实例是否含有_omi_removed属性
let node = this._createHiddenNode();
if (!isFirst) {
this.node.parentNode.replaceChild(node, this.node);
this.node = node;
} else if (this.renderTo) {
this.renderTo.appendChild(node);
};
return;
};
if (this._omi_autoStoreToData) { // 新增
if(!this._omi_ignoreStoreData) {
this.data = this.$store.data;
};
};
this.storeToData(); // 调用实例的storeToData
this._generateHTMLCSS(); // 生成 html 和 css
this._extractChildren(this); // 提取孩子(就是提取嵌套标签啦) this.children.forEach((item, index) => { // 遍历孩子
this.HTML = this.HTML.replace(item._omiChildStr, this.children[index].HTML); // 替换html中child的嵌套组件的html (把嵌套组件的html和父组件合并)
});
this.HTML = scopedEvent(this.HTML, this.id); // 把html中的事件函数转成实例对应的函数方法
if (isFirst) { // 是否第一次
if (this.renderTo) { // 渲染到的dom
if (this._omi_increment) {
this.renderTo.insertAdjacentHTML('beforeend', this.HTML); //指定位置在插入
} else {
this.renderTo.innerHTML = this.HTML; // 把html插入到渲染dom中
};
};
} else {
if (this.HTML !== "") { // 不是第一次插入就是用morphdom跟新节点
morphdom(this.node, this.HTML);
} else {
morphdom(this.node ,this._createHiddenNode());
};
};
// get node prop from parent node
if (this.renderTo) { // 有渲染到的节点
this.node = document.querySelector("[" + this._omi_scoped_attr + "]"); // render()html字符串中的根节点
this._queryElements(this); // 查询dom元素
this._fixForm();
};
}

这里面对于此demo最重要的是this._extractChildren(this);方法了,进去看看

    _extractChildren(child) {    // child: Component类的实例(一般是Component类子类的实例)
if (Omi.customTags.length > 0) { // 自定义标签名集合的长度大于0
child.HTML = this._replaceTags(Omi.customTags, child.HTML); // 返回 组件嵌套被替换成child开头的标签 看_replaceTags方法
};
let arr = child.HTML.match(/<child[^>][\s\S]*?tag=['|"](\S*)['|"][\s\S]*?><\/child>/g); // 寻找child的标签放到数组中 if(arr){
arr.forEach( (childStr, i) =>{ // 遍历每一个child标签
let json = html2json(childStr); // 把标签转换成对象
let attr = json.child[0].attr; // 取出 child标签的所有属性
let name = attr.tag; // 组件标签名
delete attr.tag; // 删除 attr对象的tag属性(即删除了标签名, 省的循环)
let cmi = this.children[i]; // 当前实例的第i的孩子
//if not first time to invoke _extractChildren method(如果不是第一次调用_extractchildren方法)
if (cmi && cmi.___omi_constructor_name === name) { // 有孩子 且 孩子的函数名等于组件标签名
cmi._childRender(childStr); // 实例渲染组件标签
} else {
let baseData = {}; // 基础数据 (on-)
let dataset = {}; // 数据设置 (data-)
let dataFromParent = {}; // 从实例中获取标签属性data值对应的实例属性值 (data)
let groupData = {}; // 组数据 (group-data)
let omiID = null; // omiId (omi-id)
let instanceName = null; // 标签名类的实例名 (name)
Object.keys(attr).forEach(key => { // 遍历嵌套组件标签中的每一个属性
const value = attr[key]; // 属性值
if (key.indexOf('on') === 0) { // 应该是事件
let handler = child[value];
if (handler) {
baseData[key] = handler.bind(child);
};
} else if (key === 'omi-id'){ // omi-id
omiID = value;
}else if (key === 'name'){ // name
instanceName = value;
}else if (key === 'group-data') { // group-data
if (child._omiGroupDataCounter.hasOwnProperty(value)) {
child._omiGroupDataCounter[value]++;
} else {
child._omiGroupDataCounter[value] = 0;
};
groupData = this._extractPropertyFromString(value,child)[child._omiGroupDataCounter[value]]; } else if(key.indexOf('data-') === 0){ // 以data-开头的属性
dataset[this._capitalize(key.replace('data-', ''))] = value;
}else if(key === 'data'){ // data
dataFromParent = this._extractPropertyFromString(value,child); //获取在child上的value属性值
};
}); let ChildClass = Omi.getClassFromString(name); // 根据标签名获取组件类 (name: 标签名)
if (!ChildClass) throw "Can't find Class called [" + name+"]"; // 没找到组件类
let sub_child = new ChildClass( Object.assign(baseData,child.childrenData[i],dataset,dataFromParent,groupData ),false);
sub_child._omiChildStr = childStr; // 子组件的_omiChildStr属性 值为组件标签
sub_child.parent = child; // 子组件的parent属性 值为子组件的父组件
sub_child.$store = child.$store; // 存储数据
if(sub_child.$store){
sub_child.$store.instances.push(sub_child);
};
sub_child.___omi_constructor_name = name; // 添加这个属性, 2310用到
sub_child._dataset = {}; // _dataset属性
sub_child.install(); // 子组件的install方法 omiID && (Omi.mapping[omiID] = sub_child); // omi-id对应的值, 然后给omi的mapping对象添加omi-id对应的值:嵌套组件的实例
instanceName && (child[instanceName] = sub_child); // 给实例添加name的值instanceName为属性 值为嵌套组件的实例 if (!cmi) { // 没有cmi就把嵌套组件的实例添加到child.children中
child.children.push(sub_child);
} else { // 否则替换
child.children[i] = sub_child;
}; sub_child._childRender(childStr,true); // 嵌套组件渲染 参数(转换后的标签, true)
};
});
};
}

1. 那里就是帮我们自动实例化了。

2.这里我们进去看看

    _childRender(childStr,isFirst) {    //childStr: 转换后的标签  isFirst: true
if (this._omi_removed ) {
this.HTML = '<input type="hidden" omi_scoped_'+this.id+' >';
return this.HTML;
};
//childStr = childStr.replace("<child", "<div").replace("/>", "></div>")
this._mergeData(childStr); // 数据合并
if(this.parent._omi_autoStoreToData) {
this._omi_autoStoreToData = true;
if (!this._omi_ignoreStoreData) {
this.data = this.$store.data;
};
};
this.storeToData();
this._generateHTMLCSS(); // 生成 html 和 css
this._extractChildren(this); // 子组件提取孩子标签(就是提取嵌套标签啦) this.children.forEach((item, index) => { // 遍历孩子
this.HTML = this.HTML.replace(item._omiChildStr, this.children[index].HTML); // 替换html中child的嵌套组件的html
});
this.HTML = scopedEvent(this.HTML, this.id); // 把html中的事件函数转成实例对应的函数方法
return this.HTML;
}

这里其实已经帮我们把List实例化的对象instance_list生成了css和html还有内置事件也绑定好了。那怎么和父组件合并呢?其实代码类似了

断点回到了_render方法中,见下图

在之后回到Omi.render方法中,

最后返回实例,前2个语句是组件的生命周期,后续再讲。

至此,组件的demo就讲完了。

ps:

来句官网的话:绝大部分Web网页或者Web应用,需要嵌套定义的组件来完成所有的功能和展示,所以组件很重要。

Omi框架学习之旅 - 组件 及原理说明的更多相关文章

  1. Omi框架学习之旅 - 组件通讯(data通讯) 及原理说明

    接着上一篇的data-*通讯,这篇写data通讯. data通讯主要为了复杂的数据通讯. 老规矩:先上demo代码, 然后提出问题, 之后解答问题, 最后源码说明. class Hello exten ...

  2. Omi框架学习之旅 - 组件通讯(group-data通讯) 及原理说明

    childrenData的方式可以批量传递数据给组件,但是有很多场景下data的来源不一定非要都从childrenData来, childrenData是个数组,会和组件的顺序一一对应,这就给不同传递 ...

  3. Omi框架学习之旅 - 组件通讯(data-*通讯) 及原理说明

    上一篇文章说了omi中的组件,以及组件如何使用及嵌套. 那omi中的组件是怎么通讯的呢? 其实omi提供的通讯方式比较丰富,各有千秋,各有各的场景用途.所以按需使用即可. 老规矩:先上demo代码, ...

  4. Omi框架学习之旅 - 之开篇扯蛋

    说实话, 我也不知道Omi是干啥的, 只因此框架是alloyTeam出的, dntzhang写的, 也有其他腾讯大神参与了, 还有一些其他贡献者, 以上我也不太清楚, 当我胡说八嘎. 因其写法有人说好 ...

  5. Omi框架学习之旅 - 通过omi-id来实现组件通讯 及原理说明

    这个demo是通过omi-id来获取子类的实例,然后更改data属性,之后updata一下就好了. 老规矩:先上demo代码, 然后提出问题, 之后解答问题, 最后源码说明. class Hello ...

  6. Omi框架学习之旅 - 通过对象实例来实现组件通讯 及原理说明

    组件通讯不是讲完了吗(上帝模式还没讲哈),怎么又多了种方式啊. 你484傻,多一种选择不好吗? 其实这个不属于组件通讯啦,只是当父组件实例安装和渲染完毕后,可以执行installed这个方法(默认是空 ...

  7. Omi框架学习之旅 - 插件机制之omi-router及原理说明

    先来看看官网的介绍吧:https://github.com/AlloyTeam/omi/tree/master/plugins/omi-router 其实我推荐直接看官网的介绍.我所写的,主要给个人做 ...

  8. Omi框架学习之旅 - 插件机制之omi-finger 及原理说明

    以前那篇我写的alloyfinger源码解读那篇帖子,就说过这是一个很好用的手势库,hammer能做的,他都能做到, 而且源码只有350来行代码,很容易看懂. 那么怎么把这么好的库作为omi库的一个插 ...

  9. Omi框架学习之旅 - Hello World 及原理说明

    学什么东西都从hello world开始, 我也不知道为啥. 恩,先上demo代码, 然后提出问题, 之后解答问题, 最后源码说明. hello world - demo: class Hello e ...

随机推荐

  1. Mysql数据库多表查询

    一.介绍 首先说一下,我们写项目一般都会建一个数据库,那数据库里面是不是存了好多张表啊,不可能把所有的数据都放到一张表里面,肯定要分表来存数据,这样节省空间,数据的组织结构更清晰,解耦和程度更高,但是 ...

  2. React中使用百度地图API

    今天我们来搞一搞如何在React中使用百度地图API好吧,最近忙的头皮发麻,感觉身体被掏空,所以很久都没来写博客了,但今天我一定要来一篇好吧 话不多说,我们直接开始好吧 特别注意:该React项目是用 ...

  3. React 入门学习笔记整理(三)—— 组件

    1.定义组件 1)函数组件 function GreateH(props){ return <div> <h2>hello,{props.name}</h2> &l ...

  4. REM在edge浏览器中不重新计算解决

    经过多分析和排查,此问题解决的方案 第一种: 在CSS样式中添加 body { font-size:100% } 如果不起作用,可以尝试将引用的REMjs放在head内引用

  5. css div相对屏幕永远居中

    不管屏幕如何滑动,该div始终保持在屏幕正中央(支持IE7(包括IE7)以上版本) <div class="loginBox"></div> .loginB ...

  6. beego+vue.js分离开发,结合发布,简单部署

    大家知道,golang开发的东西部署简单是它很大的卖点,一般的应用,生成的可执行文件直接放服务器上运行即可,不需要任何环境.当然,大型的应用才需要比如mysql,nginx等. 但是当vue.js出现 ...

  7. LyX快捷键管理

    快捷键修改:Tools->Preference->Editing->Shortcuts:修改后要Tools->Reconfig生效 快捷键默认保存文件:%appdata%\Ly ...

  8. recovery log直接输出到串口

    我们在调试recovery升级的时候,我们经常需要查看recovery的log,google的原始逻辑中,recovery的log并非直接输出到串口,我们需要输入命令才能获取,我们有三种方式: 第一种 ...

  9. SQL Server 将一个表中字段的值复制到另一个表的字段中

    具体方法如下 一:update 表2 set (要插入的列名)= select 表1.某一列 from 表1 left jion 表2 on 表1和表2的关联 where ..... 二:update ...

  10. 2018. first week now at home

    外面雪刚停. 现在是2018.1.5 2018 needs to consider next steps了.未雨绸缪啊     下面是2017年last working day   外面黑了,水面上黑 ...