Vue3 源码解读系列(十三)——双向数据绑定 v-model

v-model

本质是 prop事件监听 的语法糖。

通过 prop 实现 数据 -> 视图 的单向数据流;通过监听 change 或 input 事件实现 视图 -> 数据 的单向数据流。

js 复制代码
/**
 * v-model 的实现
 * 注册了 created 和 beforeUpdate 两个钩子函数
 */
const VModelText = {
  /**
   * created 钩子函数
   * @param {Object} el - 节点对象
   * @param {Object} binding - binding 对象
   * @param {Object} vnode - 虚拟节点对象
   * created 主要做了 3 件事:
   * 1、将 js 对象的 value 赋值为元素的 value 属性
   * 2、通过 getModelAssigner 方法获取 props 中的 onUpdate:modelValue 属性赋值给元素的 _assign 属性
   * 3、通过 addEventListener 监听 input 标签的事件
   */
  created(el, { value, modifiers: { lazy, trim, number } }, vnode) {
    // 1、将 js 对象的 value 赋值为元素的 value 属性(数据 -> 视图的单向数据流)
    el.value = value == null ? '' : value

    // 2、通过 getModelAssigner 方法获取 props 中的 onUpdate:modelValue 属性赋值给元素的 _assign 属性
    el._assign = getModelAssigner(vnode)

    // 判断是否配置了 number 或 元素的 type 为 number
    const castToNumber = number || el.type === 'number'

    // 3、通过 addEventListener 监听 input 标签的事件
    // 根据 lazy 决定监听元素的 change 或 input 事件(change | input)
    addEventListener(el, lazy ? 'change' : 'input', e => {
      if (e.target.composing) return

      // 获取元素的新值
      let domValue = el.value

      // 如果配置了 trim,则调用 String.trim() 清除首尾空格
      if (trim) {
        domValue = domValue.trim()
      }
      // 如果配置了 number 或元素的 type 为 number,则转换成 number 后再赋值给元素
      else if (castToNumber) {
        domValue = toNumber(domValue)
      }

      // 更新数据
      el._assign(domValue)
    })

    // 如果配置了 trim,则调用 String.trim() 清除首尾空格
    if (trim) {
      addEventListener(el, 'change', () => {
        el.value = el.value.trim()
      })
    }

    // 如果没有配置 lazy,则添加两个事件对中文输入法输入进行监听
    if (!lazy) {
      addEventListener(el, 'compositionstart', onCompositionStart)
      addEventListener(el, 'compositionend', onCompositionEnd)
    }
  },
  /**
   * beforeUpdata 钩子函数
   * @param {Object} el - 节点对象
   * @param {Object} binding - binding 对象
   * @param {Object} vnode - 虚拟节点对象
   */
  beforeUpdate(el, { value, modifiers: { trim, number } }, vnode) {
    el._assign = getModelAssigner(vnode)
    if (document.activeElement === el) {
      // 如果配置了 trim,则将元素的值通过 String.trim() 清除首尾空格和再赋值给数据
      if (trim && el.value.trim() === value) return

      // 如果配置了 number 或 元素的 type 为 number,则将元素的值转换为 number 再赋值给数据
      if ((number || el.type === 'number') && toNumber(el.value) === value) return
    }
    const newValue = value == null ? '' : value
    // 更新前判断新旧值是否相同,如果不同,则把数据更新到 DOM 上
    if (el.value !== newValue) {
      el.value = newValue
    }
  }
}

/**
 * 获取 model 分配器
 */
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

    // 手动触发 input 事件进行赋值
    trigger(target, 'input')
  }
} 
相关推荐
Setsuna_F_Seiei2 小时前
前端转型 Agent 开发 05 之 Agent Hooks 与 Checkpointer(让 Agent 从全自动转变人为可掌控)
前端·agent·ai编程
百万蹄蹄向前冲4 小时前
风扇转了一晚上MVP专家团翻车事故
前端·人工智能
默_笙4 小时前
🏛 给 AI 配一间办公室:Harness Engineering 六大模块与它的实现
前端·javascript
linux_cfan5 小时前
videojs v10 源代码系列解读:14 · 谓词守卫:在运行时安全地调用能力
前端·javascript·音视频
kyriewen6 小时前
我扒了 10,221 条 JD:腾讯技术岗 75% 在要 AI
前端·人工智能·ai编程
郑州光合科技余经理6 小时前
同城外卖小程序开发:下单成功后,后台导出能不能对上用户端状态
开发语言·前端·git·后端·uni-app·php·ai编程
+VX:Fegn08957 小时前
计算机毕业设计|基于springboot + vue旅游管理系统(源码+数据库+文档)
java·开发语言·vue.js·spring boot·课程设计
雪芽蓝域zzs7 小时前
第 7 章:双 Y 轴组合图(柱状 + 折线,看板高频场景)
vue.js·echars
IT_陈寒7 小时前
Vue的响应式让我熬到凌晨三点,原来漏了这个小细节
前端·人工智能·后端
一 乐7 小时前
二手交易平台|基于springboot + vue二手交易平台(源码+数据库+文档)
java·数据库·vue.js·spring boot·小程序