• Vue源码后记-其余内置指令(2)


      ……

      指令这个讲起来还有点复杂,先把html弄上来:

        <body>
            <div id='app'>
                <div v-if="vIfIter" v-bind:style="styleObject">
                    <input v-show="vShowIter" v-model='vModel' />
                    <span v-once>{{msg}}</span>
                    <div v-html="html"></div>
                </div>
                <div class='on'>empty Node</div>
            </div>
        </body>
        <script src='./vue.js'></script>
        <script>
            var app = new Vue({
                el: '#app',
                data: {
                    vIfIter: true,
                    vShowIter: true,
                    vModel: 1,
                    styleObject: {
                        color: 'red'
                    },
                    msg: 'Hello World',
                    html: '<span>v-html</span>'
                },
            });
        </script>

      上一节是解析完了input标签的2个属性,并将其打包进了directives属性中返回。

      接着会继续跑genData函数,如下:

        function genData(el) {
            var data = '{';
    
            // 从这出来
            var dirs = genDirectives(el);
            if (dirs) {
                data += dirs + ',';
            }
    
            // code...
    
            // DOM props
            if (el.props) {
                data += "domProps:{" + (genProps(el.props)) + "},";
            }
            // event handlers
            if (el.events) {
                data += (genHandlers(el.events, false, warn$3)) + ",";
            }
            
            // code...
    
            data = data.replace(/,$/, '') + '}';
            // v-bind data wrap
            if (el.wrapData) {
                data = el.wrapData(data);
            }
            return data
        }

      由于在处理v-model的时候给el添加了props与events属性,所以之后会跑进两个处理函数。

      首先看看genProps:

        // props => {name:value,value:(vModel)}
        function genProps(props) {
            var res = '';
            for (var i = 0; i < props.length; i++) {
                var prop = props[i];
                res += """ + (prop.name) + "":" + (transformSpecialNewlines(prop.value)) + ",";
            }
            return res.slice(0, -1)
        }
    
        // 替换新换行符
        function transformSpecialNewlines(text) {
            return text
                .replace(/u2028/g, '\u2028')
                .replace(/u2029/g, '\u2029')
        }

      比较简单,直接看返回的字符串:

      

      接下来是处理添加的events:

        // event handlers
        if (el.events) {
            data += (genHandlers(el.events, false, warn$3)) + ",";
        }
    
        function genHandlers(events, native, warn) {
            var res = native ? 'nativeOn:{' : 'on:{';
            for (var name in events) {
                var handler = events[name];
    
                // click.right => contextmenu
                // 右键并不能触发click事件 建议使用H5新属性 然而这个属性兼容性捉急
    
                res += """ + name + "":" + (genHandler(name, handler)) + ",";
            }
            return res.slice(0, -1) + '}';
        }
    
        // name => inpout
        // handler => {modifiers => null,value:'if(...)...'}
        function genHandler(name, handler) {
            if (!handler) {
                return 'function(){}'
            }
    
            if (Array.isArray(handler)) {
                return ("[" + (handler.map(function(handler) {
                    return genHandler(name, handler);
                }).join(',')) + "]")
            }
    
            // simplePathRE => /^s*[A-Za-z_$][w$]*(?:.[A-Za-z_$][w$]*|['.*?']|[".*?"]|[d+]|[[A-Za-z_$][w$]*])*s*$/;
            // 这是匹配的啥玩意啊!
            var isMethodPath = simplePathRE.test(handler.value);
            // fnExpRE => /^s*([w$_]+|([^)]*?))s*=>|^functions*(/;
            // 匹配箭头函数或function
            var isFunctionExpression = fnExpRE.test(handler.value);
    
            // 匹配完 两个都是false
            // 包装成函数形式返回
            if (!handler.modifiers) {
                return isMethodPath || isFunctionExpression ?
                    handler.value :
                    ("function($event){" + (handler.value) + "}") // inline statement
            }
            // 处理事件后缀
            else {
                // code...
            }
        }

      事件处理方面也很明了,首先判断是原生事件还是自定义,然后根据表达式的形式进行处理返回,本例会被包装为一个普通的函数返回,如图:

      

      字符串代表DOM上有一个input事件,值为"on:..."

      至此,input标签AST转换完毕,返回的code字符串如图:

        "_c('input',
        {directives:[{name:"show",rawName:"v-show",value:(vShowIter),expression:"vShowIter"},{name:"model",rawName:"v-model",value:(vModel),expression:"vModel"}],
        domProps:{"value":(vModel)},
        on:{"input":function($event){if($event.target.composing)return;vModel=$event.target.value}}})"

      包含tag名、内置指令v-model/v-show、props、events。

      下面处理span标签,包含一个v-once:

        <span v-once>{{msg}}</span>

      这个el有个once属性,会进入genOnce函数:

        function genOnce(el) {
            el.onceProcessed = true;
            // 优先处理v-if
            if (el.if && !el.ifProcessed) {
                return genIf(el)
            } else if (el.staticInFor) {
                // 同时出现v-once与v-for
            } else {
                return genStatic(el)
            }
        }
    
        // hoist static sub-trees out
        function genStatic(el) {
            el.staticProcessed = true;
            // 该节点属于静态dom
            staticRenderFns.push(("with(this){return " + (genElement(el)) + "}"));
            return ("_m(" + (staticRenderFns.length - 1) + (el.staticInFor ? ',true' : '') + ")")
        }
    
        function genElement(el) {
            if (el.staticRoot && !el.staticProcessed) {
                // once/for/if/templte/slot
            } else {
                // component or element
                var code;
                if (el.component) {
                    code = genComponent(el.component, el);
                } else {
                    var data = el.plain ? undefined : genData(el);
    
                    var children = el.inlineTemplate ? null : genChildren(el, true);
                    code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
                }
                // module transforms => return code
            }
        }

      由于v-once是一次性渲染,所以会被归类于静态渲染,render函数会被弹入staticRenderFns数组。

      这里还有一个值得注意的是,由于span节点只有v-once,并没有其余的属性,没有必要进入genData进行属性切割,所以早在parseHTML阶段,该el的plain属性被置为true,跳过genData阶段。

        // parseHTML => processRawAttrs
        function processRawAttrs(el) {
            var l = el.attrsList.length;
            if (l) {
                // code...
            } else if (!el.pre) {
                el.plain = true;
            }
        }

      呃……

      没有属性处理会genChildren,子节点是一个表达式,会返回一个如图字符串:,最开始跑源码的时候见过_v、_s,不用的是该表达式被弹入了staticRenderFns数组。

      至此,span标签解析完毕。

      下面开始解析下一个div标签:

        <div v-html="html"></div>

      该标签包含一个内置指令,所以会进入genDirectives分支,并调用对应的gen函数处理v-html:

        function html(el, dir) {
            // dir.value => html
            if (dir.value) {
                addProp(el, 'innerHTML', ("_s(" + (dir.value) + ")"));
            }
        }

      代码很简单,将v-html对应的值用_s包裹起来,添加到props属性中,如图:

      这里需要注意的是,该函数并未返回任何值,因为v-html不需要弄进directives属性,所以默认返回的是undefined,直接会跳过后面条件判断返回。

      因为设置了props属性,所以后面的genProps也要跑,过程不看了直接看结果:

      至此,v-html的标签解析完了,返回一个render函数:

      下面解析根元素的下一个子元素,静态div:

        <div class='on'>empty Node</div>

      调用流程是这样的:generate => genChildren => genNode => genElement => genData && genChildren

      节点包含一个静态的类以及字符串子节点,所以会调用最后的genData与genChildren返回一个render字符串,流程比较简单,所以直接放结果:

     

      到此为止,所有的AST都被转化为render函数,可以看一下结构:

        _c('div' /*<div id='app'>*/ , {
            attrs: {
                "id": "app"
            }
        }, [(vIfIter) /*v-if条件*/ ?
            // 条件为真渲染下面的DOM
            _c('div' /*<div v-if="vIfIter" v-bind:style="styleObject">*/ , {
                style: (styleObject)
            }, [_c('input' /*<input v-show="vShowIter" v-model='vModel' />*/ , {
                    directives: [{
                        name: "show",
                        rawName: "v-show",
                        value: (vShowIter),
                        expression: "vShowIter"
                    }, {
                        name: "model",
                        rawName: "v-model",
                        value: (vModel),
                        expression: "vModel"
                    }],
                    domProps: {
                        "value": (vModel)
                    },
                    on: {
                        "input": function($event) {
                            if ($event.target.composing) return;
                            vModel = $event.target.value
                        }
                    }
                }),
                _v(" ") /*这些是回车换行符*/ ,
                _m(0) /*<span v-once>{{msg}}</span>*/ , _v(" "),
                _c('div' /*<div v-html="html"></div>*/ , {
                    domProps: {
                        "innerHTML": _s(html)
                    }
                })
            ]) :
            // 否则渲染一个空的div...(错了)
            _e() /*comment*/ ,
            _v(" "),
            _c('div' /*<div class='on'>empty Node</div>*/ , {
                staticClass: "on"
            }, [_v("empty Node")])
        ])

      这样看起来结构就非常清晰了,render函数针对v-if是使用一个三元表示式处理的,该函数的整体框架结构为_c(tagName,attrs,childrens),其中childrens可能嵌套多个_c。

      其中有个小点需要注意的是里面并没有v-once的那个span标签,取而代之的是一个_m(0),这是因为span标签是静态节点,所以被弹入staticRenderFns数组并作为第一个元素,这里将来会将其取出来并渲染。

      这个patch下次再讲,最近被项目搞的脑袋疼。

  • 相关阅读:
    (转载)链表环中的入口点 编程之美 leecode 学习
    leecode single numer
    leecode 树的平衡判定 java
    Let the Balloon Rise
    Digital Roots
    大数加法,A+B
    小希的迷宫
    畅通工程
    lintcode596- Minimum Subtree- easy
    lintcode597- Subtree with Maximum Average- easy
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/7363625.html
Copyright © 2020-2023  润新知