深入了解v-model流程__Vue.js
发布于 4 年前 作者 banyungong 1323 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

v-model原理

vue中v-model是一个语法糖,所谓的语法糖就是对其他基础功能的二次封装而产生的功能。简单点说,v-model本身就是父组件对子组件状态以及状态改变事件的封装。其实现原理上分为两个部分:

  • 通过props设置子组件的状态
  • 通过监听子组件发出的事件改变父组件的状态,从而影响子组件的props值

通过以上两个部分,实现了父组件的状态和子组件状态进行了绑定的效果。

demo

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>v-model示例</title>
        <script type="text/javascript" src="vue.js"></script>
    </head>

    <body>
        <div id="app">
            <div>这里是父组件的状态:</div>
            <div style="margin-bottom: 15px;">{{content}}</div>
            <Child v-model="content"></Child>
        </div>

        <template id="input">
            <div>
                <div>这里是子组件的输入区域:</div>
                <input :value="value" @input="contentChange" />
            </div>
        </template>

        <script type="text/javascript">
        var Child = {
            template: "#input",
            props: {
                value: {
                    type: String,
                    required: true
                }
            },
            methods: {
                contentChange(value){
                    this.$emit("input", value.target.value);
                }
            }
        };

        var vueInstance = new Vue({
            el: "#app",
            components: {Child},
            data: {
                content: ""
            }
        })
        </script>
    </body>
</html>

在浏览器中打开上述html页面,可以看到实时效果:在子组件中的input框中输入内容可以在父组件区域实时显示,达到了子组件中状态和父组件状态实时绑定的效果。

修改v-model默认监听的事件和设置prop的名称

v-model指令默认是在子组件上设置的prop名称是value,默认监听子组件上的input事件,在上面的demo上,如果我们修改子组件contentChange函数中发出的事件名称,在父组件中就无法实时获取到子组件的输入。

Vue中提供了通过在子组件上定义model属性来修改这两个参数名称的功能,不过该功能需要在版本2.2以上才能使用,如下demo所示:

demo

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>v-model示例</title>
        <script type="text/javascript" src="vue.js"></script>
    </head>

    <body>
        <div id="app">
            <div>这里是父组件的状态:</div>
            <div style="margin-bottom: 15px;">{{content}}</div>
            <Child v-model="content"></Child>
        </div>

        <template id="input">
            <div>
                <div>这里是子组件的输入区域:</div>
                <input :value="content" @input="contentChange" />
            </div>
        </template>

        <script type="text/javascript">
        var Child = {
            template: "#input",
            model: {
                prop: "content",
                event: "contentChanged"
            },
            props: {
                content: {
                    type: String,
                    required: true
                }
            },
            methods: {
                contentChange(value){
                    this.$emit("contentChanged", value.target.value);
                }
            }
        };

        var vueInstance = new Vue({
            el: "#app",
            components: {Child},
            data: {
                content: ""
            }
        })
        </script>
    </body>
</html>

Vue中对v-model指令处理分析

基于Vue2.0版本,分析我们在标签上写上v-model属性到vue组件实现响应的流程。

解析部分

在将HTML解析称AST时,会解析HTML中标签的属性
function processAttrs(el){
  ...
  name = name.replace(dirRE, '')
  // parse arg
  const argMatch = name.match(argRE)
  if (argMatch && (arg = argMatch[1])) {
    name = name.slice(0, -(arg.length + 1))
  }
  addDirective(el, name, value, arg, modifiers)
  ...
}

提取指令的名称,v-model的指令名称name为model,然后添加到实例的指令中

将指令相关内容添加到实例指令中
export function addDirective (
  el: ASTElement,
  name: string,
  value: string,
  arg: ?string,
  modifiers: ?{ [key: string]: true }
) {
  (el.directives || (el.directives = [])).push({ name, value, arg, modifiers })
}

在实例的指令属性中添加相应的指令,这样就实现了从html上的属性到Vue实例上指令格式的转换

指令设置部分

在将html解析称AST之后,实例对应的directives属性上就有了我们设置的v-model相关的值,包括参数值value,name是model

调用指令的构造函数
function genDirectives (el: ASTElement): string | void {
  const dirs = el.directives
  if (!dirs) return
  let res = 'directives:['
  let hasRuntime = false
  let i, l, dir, needRuntime
  for (i = 0, l = dirs.length; i < l; i++) {
    dir = dirs[i]
    needRuntime = true
    const gen = platformDirectives[dir.name] || baseDirectives[dir.name]
    if (gen) {
      // compile-time directive that manipulates AST.
      // returns true if it also needs a runtime counterpart.
      needRuntime = !!gen(el, dir, warn)
    }
    ...
}

在v-model指令的构造函数中会根据tag的种类进行不同的创建函数进行创建,如果我们自定义指令需要在子组件上添加属性,也需要在这个函数里面进行操作

普通tag下的v-model指令构造过程
function genDefaultModel 
  el: ASTElement,
  value: string,
  modifiers: ?Object
): ?boolean {
  ...
  addProp(el, 'value', isNative ? `_s(${value})` : `(${value})`)
  addHandler(el, event, code, null, true)
  ...
}
  • addProp在el上设置一个名称为value的prop,同时设置其值
  • addHandler在el上设置事件处理函数

指令响应变化部分

createPatchFunction统一处理指令的钩子函数

createPatchFunction函数返回一个patch函数,在patch处理过程中,会调用指令的钩子函数,包括:

  • bind
  • inserted
  • update
  • componentUpdated
  • unbind

总结

编译过程

  1. 从html上解析所设置的指令
  2. 通过gen*函数将指令设置到AST上
  3. 调用指令的构造函数,设置指令需要在编译时期处理的事情

初始化过程

通过在patch函数中,调用统一的钩子函数,触发指令的钩子函数,实现相应的功能

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: flyingbird 原文链接:https://juejin.im/post/6865945597875896333

回到顶部