黑马程序员技术交流社区

标题: 【成都校区】你真的了解 v-model 吗? [打印本页]

作者: 小蜀哥哥    时间: 2019-11-21 23:12
标题: 【成都校区】你真的了解 v-model 吗?
众所周知,v-model 是 Vue.js 中实现的一个语法糖,和 Vue.js 中推崇的单向数据流表现不一致,用于实现所谓的双向绑定。
但看似简单的 v-model 具体是怎么做到双向绑定的,为了满足下好奇心,不得不深入到源码中看一看。
v-model 的使用情景分为两种:直接用到 input 或 textarea 等输入控件中;用于自定义组件中。之所以分为这两类是因为它们在 Vue 源码中的实现的有差异的。
在输入控件中使用 v-model
[HTML] 纯文本查看 复制代码
<div id="app">
    <input type="text" v-model="aa" >
</div>
<script>
export default {
    data() {
        return {
            aa: 'hello'
        }
    }
}
</script>

写一个如上的单页面组件,首先会发生什么?
如果我们用的是不带编译器版本的 Vue , 那么 vue-loader 会将其这个文件编译为 一个 render 函数,结果如下:
[JavaScript] 纯文本查看 复制代码
(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"app"}},[_c('input',{directives:[{name:"model",rawName:"v-model",value:(aa),expression:"aa"}],attrs:{"type":"text"},domProps:{"value":(aa)},on:{"input":function($event){if($event.target.composing)return;aa=$event.target.value}}})])}
})

着重看 input 被编译出来的结果:
[JavaScript] 纯文本查看 复制代码
 [_c('input',
        {directives:[{name:"model",rawName:"v-model",value:(aa),expression:"aa"}],
     attrs:{"type":"text"},domProps:{"value":(aa)},
     on:{"input":function($event){if($event.target.composing)return;aa=$event.target.value}}})]
从上面就可以大概看出所谓语法糖的由来了,首先在为 input  设置了名为 value 的属性,再在 on 中写了一个 input 事件。看上去也很简单,但这些属性和事件又是怎么和真实 DOM 挂上钩的?从下面几个点分析:

[JavaScript] 纯文本查看 复制代码
// 执行平台相关的 DOM 属性操作以及事件监听操作
for (let i = 0; i < cbs.create.length; ++i) {
  cbs.create(emptyNode, vnode)
}


可能你又要问这个 cbs.create 是哪来的?由于不同平台下 DOM 的有关操作差异很大,为了实现跨平台, Vue 将这些操作都单独封装起来。如果要看 web 平台相关的详细逻辑可以去 “src\platforms\web\runtime\modules\index.js”  中查找。今天我们只看 domProps 和事件监听的处理:
[JavaScript] 纯文本查看 复制代码
// src\platforms\web\runtime\modules\dom-props.js
export default {
  create: updateDOMProps,
  update: updateDOMProps
}

可以看到这个文件输出了一个对象,这个对象又有一个 create 属性, invokeCreateHooks 函数中调用的函数正是这个属性的值 updateDOMProps。 updateDOMProps 负责处理 innerText, innerHTML, value 等逻辑。
[JavaScript] 纯文本查看 复制代码
// src\platforms\web\runtime\modules\events.js
export default {
  create: updateDOMListeners,
  update: updateDOMListeners
}

同样的,事件监听的逻辑也类似。updateDOMListeners 根据 VNode.data.on 中的逻辑处理事件监听器的更新,挂载和删除。除此之外,directives 中编译出的 v-model 实现方式与前面一致,Vue 中有对 v-model 这个执行做特殊处理,解决了一些浏览器兼容性上的问题,以及不同输入控件的兼容问题,有兴趣的小伙伴可以看看源码中 “src\platforms\web\runtime\directives\model.js” 的处理。
在自定义组件中使用 v-model
[HTML] 纯文本查看 复制代码
<div id="app">
    <compo v-model="aa" />
</div>
<script>
export default {
    data() {
        return {
            aa: 'hello'
        }
    }
}
</script>
// compo
  const compo = Vue.component('compo', {
    template: '<input :value="$attrs.value" @input="handleInput" />',
    methods: {
      handleInput(e) {
        this.$emit('input', e.target.value)
      }
    },
  })

compo 中就能清楚的看到用于两种情景下 v-model 的差异,需要在组件中的input元素上手动去绑定一个 @input 事件,并让其给父组件传递一个 input 事件。先看看这种情况下编译出来的结果:
[JavaScript] 纯文本查看 复制代码
(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"app"}},[_c('compo',{model:{value:(aa),callback:function ($$v) {aa=$$v},expression:"aa"}})],1)}
})
// compo 编译结果
[_c('compo',{model:{value:(aa),callback:function ($$v) {aa=$$v},expression:"aa"}})]

大家发现问题没有,组件中的 v-model 并没有编译出 input 事件,只是一个简单的 callback。那要如何在源码中找到响应处理呢?
组件由 render  函数变为  VNode 的主要逻辑在 “src\core\vdom\create-component.js” 中的 createComponent 函数,以后出现类似问题都可以从这个函数入手。这个函数中对组件的 model 属性有如下的特殊处理:

[JavaScript] 纯文本查看 复制代码
  if (isDef(data.model)) {
    transformModel(Ctor.options, data)
  }
  
// transform component v-model info (value and callback) into
// prop and event handler respectively.
function transformModel (options, data: any) {
  const prop = (options.model && options.model.prop) || 'value'
  const event = (options.model && options.model.event) || 'input'
  ;(data.attrs || (data.attrs = {}))[prop] = data.model.value
  const on = data.on || (data.on = {})
  const existing = on[event]
  const callback = data.model.callback
  if (isDef(existing)) {
    if (
      Array.isArray(existing)
        ? existing.indexOf(callback) === -1
        : existing !== callback
    ) {
      on[event] = [callback].concat(existing)
    }
  } else {
    on[event] = callback
  }
}

现在回头看 callback 是怎么和 input 事件挂钩是不是一目了然了,同时里面还有组件中 model 属性的配置,比如,不想要绑定 input 事件,要处理 change 事件,也是在这段源码中实现的。
所以,我们回头来总结下:









欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2