vue3学习笔记--v-mdel原理

131 阅读3分钟

v-model 也不是可以作用到任意标签,它只能在一些特定的表单标签如 input、select、textarea 和自定义组件中使用。

在普通表单元素上作用v-model

基本示例

<input v-model="searchText">

模板编译后生成的render函数

import { vModelText as _vModelText, createVNode as _createVNode, withDirectives as _withDirectives, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return _withDirectives((_openBlock(), _createBlock("input", {
    "onUpdate:modelValue": $event => (_ctx.searchText = $event)
  }, null, 8 /* PROPS */, ["onUpdate:modelValue"])), [
    [_vModelText, _ctx.searchText]
  ])
}

作用在 input 标签的 v-model 指令在编译后,除了使用 withDirectives 给这个 vnode 添加了 vModelText 指令对象外,还额外传递了一个名为 onUpdate:modelValue 的 prop,它的值是一个函数,这个函数就是用来更新变量 searchText。

const vModelText = {
  created(el, { value, modifiers: { lazy, trim, number } }, vnode) {
    el.value = value == null ? '' : value //把v-model绑定的value值复制给el.value;数据到DOM的数据流
    el._assign = getModelAssigner(vnode) //获取props中的onUpdate:modelValue属性对应的函数
    const castToNumber = number || el.type === 'number'
    addEventListener(el, lazy ? 'change' : 'input', e => {//如果有lazy修饰符,添加change事件,没有的话绑定事件
      if (e.target.composing) //composing代表的是:是否是处于中文输入法
        return
      let domValue = el.value
      if (trim) { //是否有trim修饰符
        domValue = domValue.trim()
      }
      else if (castToNumber) {//是否有number修饰符
        domValue = toNumber(domValue)
      }
      el._assign(domValue) //把dom上的新的value值复制给v-model的变量。DOM到数据的流动。
    })
    if (trim) {//change事件是input输入框失去焦点,且值变化的时候触发
      addEventListener(el, 'change', () => {
        el.value = el.value.trim()
      })
    }
    if (!lazy) {//如果使用的是中文输入法,出触发compositionstart,选中某文字之后触发compositionend
      addEventListener(el, 'compositionstart', onCompositionStart)//中文输入数输入过程中不触发input事件
      addEventListener(el, 'compositionend', onCompositionEnd)//中文输入完之后手动触发input事件
    }
  },
  beforeUpdate(el, { value, modifiers: { trim, number } }, vnode) {//当组件更新时
    el._assign = getModelAssigner(vnode)
    if (document.activeElement === el) {
      if (trim && el.value.trim() === value) {//如果传进来value值和el.value没有变化,直接返回
        return
      }
      if ((number || el.type === 'number') && toNumber(el.value) === value) {
        return
      }
    }
    const newValue = value == null ? '' : value
    if (el.value !== newValue) {//传进来的新值替换掉el.value
      el.value = newValue
    }
  }
}
const getModelAssigner = (vnode) => {
  const fn = vnode.props['onUpdate:modelValue']
  return isArray(fn) ? value => invokeArrayFns(fn, value) : fn
}
function onCompositionStart(e) {
  e.target.composing = true
}
function onCompositionEnd(e) {
  const target = e.target
  if (target.composing) {
    target.composing = false
    trigger(target, 'input')
  }
}

vModelText指令实现了两个钩子函数:created和beforeUpdate
注意钩子函数的生命周期也是变了的
created 部分:第一个参数 el 是节点的 DOM 对象,第二个参数是 binding 对象,第三个参数 vnode 是节点的 vnode 对象。
1,created过程
传进来的value赋值给el.value
获取update:modelValue的事件处理函数
el的事件绑定
lazy是false是再绑定compositionstart和compositionend事件,特殊处理输入法
2,beforeUpdate组件更新
判断值有变化就把新值value赋值给el.value

在自定义组件上定义v-model

我们来看一下这个模板编译后生成的 render 函数:
import { resolveComponent as _resolveComponent, createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  const _component_custom_input = _resolveComponent("custom-input")
  return (_openBlock(), _createBlock(_component_custom_input, {
    modelValue: _ctx.searchText,
    "onUpdate:modelValue": $event => (_ctx.searchText = $event)
  }, null, 8 /* PROPS */, ["modelValue", "onUpdate:modelValue"]))
}

可以看到,编译的结果似乎和指令没有什么关系,并没有调用 withDirective 函数。

<custom-input :modelValue="searchText" @update:modelValue="$event=>{searchText = $event}"/>

改写之后得到的编译的render函数是一样的。
v-model 作用于组件上本质就是一个语法糖,就是往组件传入了一个名为 modelValue 的 prop,它的值是往组件传入的数据 data,另外它还在组件上监听了一个名为 update:modelValue 的自定义事件,事件的回调函数接受一个参数,执行的时候会把参数 $event 赋值给数据 data。

Vue.js 3.0 给组件的 v-model 提供了参数的方式,允许我们指定 prop 的名称:

<custom-input v-model:text="searchText"/>。
<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />

vue3区别于vue2
prop名称的变化:modelValue-->value
事件名的变化update:modelValue 的事件-->input事件
vue3可以直接修改prop名,vue2可以通过model配置或者通过.sync。但是vue3中.sync已经废弃了。