• Omi框架学习之旅


    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[^>][sS]*?tag=['|"](S*)['|"][sS]*?></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应用,需要嵌套定义的组件来完成所有的功能和展示,所以组件很重要。

        

    开心的做一个无忧无虑的码农,争取每天进步一点。
  • 相关阅读:
    UE4 Cel Shading(卡通渲染)
    UE4 常用数学
    锈迹材质全流程实例:Blender-》SP-》UE4
    ue4 优化建议与经验
    VisualStudio开发UE4工程设置
    Procedural Mesh Component in C++:Getting Started
    Java容器有哪些?
    java 连接mongodb
    mongodb 系列 ~ mongo 用户验证系列
    mongodb连接认证失败
  • 原文地址:https://www.cnblogs.com/sorrowx/p/6595931.html
Copyright © 2020-2023  润新知