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*=>|^function\s*\(/;
        // 匹配箭头函数或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() /*<div></div>*/ ,
        _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下次再讲,最近被项目搞的脑袋疼。

时间: 2024-08-01 10:45:27

Vue源码后记-其余内置指令(2)的相关文章

为什么小视频源码中要内置那么多的分类模块?

倘若我们将市面上的所有短视频都下载下来进行对比,我们就会发现,大概所有的短视频软件都会有"视频分类"这个模块,这个在短视频源码中,各种视频分类是必不可少的一项. 那么,第一个问题来了:为什么小视频源码中要内置那么多的分类模块?因为分类模块越多,用户类型就越多,越精准,也越利于后期变现. 小视频可以涉足的领域有很多,比如音乐.舞蹈.母婴.科普.种草.购物.搞笑--多领域的精彩内容令人目不暇接,也吸引了更多类型的客户,每种类型的客户凭借其需求可以自主选择他们想看的视频,再辅以大数据算法的支

Vue源码后记-钩子函数

vue源码的马拉松跑完了,可以放松一下写点小东西,其实源码讲20节都讲不完,跳了好多地方. 本人技术有限,无法跟大神一样,模拟vue手把手搭建一个MVVM框架,然后再分析原理,只能以门外汉的姿态简单过一下~ 想到什么写什么了,这节就简单说说钩子函数吧! vue中的钩子函数主要包含初始化的beforeCreated/created,Virtual Dom更新期间的beforeUpdate/updated,页面渲染期间的beforeMount/mounted,组件销毁期间的beforeDestroy

Vue源码后记-vFor列表渲染(2)

这一节争取搞完! 回头来看看那个render代码,为了便于分析,做了更细致的注释: (function() { // 这里this指向vue对象 下面的所有方法默认调用Vue$3.prototype上的方法 with(this){ return _c/*方法调用 => has拦截器过滤*/ ('div',{attrs:{"id":"app"}}, _l/*方法调用 => has拦截器过滤*/( (items/*_data属性访问 => 自定义pro

Vue源码后记-vFor列表渲染(3)

这一节肯定能完! 经过DOM字符串的AST转化,再通过render变成vnode,最后就剩下patch到页面上了. render函数跑完应该是在这里: function mountComponent(vm, el, hydrating) { vm.$el = el; if (!vm.$options.render) { vm.$options.render = createEmptyVNode; { // warning } } // beforeMount var updateComponen

Vue.js 源码分析(二十二) 指令篇 v-model指令详解

Vue.js提供了v-model指令用于双向数据绑定,比如在输入框上使用时,输入的内容会事实映射到绑定的数据上,绑定的数据又可以显示在页面里,数据显示的过程是自动完成的. v-model本质上不过是语法糖.它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理.例如: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <ti

vue内置指令与自定义指令

一.内置指令 1.v-bind:响应并更新DOM特性:例如:v-bind:href  v-bind:class  v-bind:title  v-bind:bb 2.v-on:用于监听DOM事件: 例如:v-on:click  v-on:keyup 3.v-model:数据双向绑定:用于表单输入等:例如:<input v-model="message"> 4.v-show:条件渲染指令,为DOM设置css的style属性 5.v-if:条件渲染指令,动态在DOM内添加或删除

vue内置指令详解——小白速会

指令 (Directives) 是带有 v- 前缀的特殊属性,职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM. 内置指令 1.v-bind:响应并更新DOM特性:例如:v-bind:href  v-bind:class  v-bind:title  等等 主要用法是绑定属性,动态更新HTML元素上的属性: <a v-bind:href="url">...</a> <!-- 缩写 --> <a :href="ur

Vue源码探究-事件系统

Vue源码探究-事件系统 本篇代码位于vue/src/core/instance/events.js 紧跟着生命周期之后的就是继续初始化事件相关的属性和方法.整个事件系统的代码相对其他模块来说非常简短,分几个部分来详细看看它的具体实现. 头部引用 import { tip, toArray, hyphenate, handleError, formatComponentName } from '../util/index' import { updateListeners } from '../

Vue源码探究-虚拟DOM的渲染

Vue源码探究-虚拟DOM的渲染 在虚拟节点的实现一篇中,除了知道了 VNode 类的实现之外,还简要地整理了一下DOM渲染的路径.在这一篇中,主要来分析一下两条路径的具体实现代码. 按照创建 Vue 实例后的一般执行流程,首先来看看实例初始化时对渲染模块的初始处理.这也是开始 mount 路径的前一步.初始包括两部分,一是向 Vue 类原型对象上挂载渲染相关的方法,而是初始化渲染相关的属性. 渲染的初始化 下面代码位于vue/src/core/instance/render.js 相关属性初始