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


    v-model

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

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

    /**
     * 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')
      }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
  • 相关阅读:
    Eigen入门
    XFS 打开电子商务新方式
    J2EE基础:MySQL01
    机器学习(6)——数据探索与可视化(2)
    SpringBoot SpringBoot 开发实用篇 3 测试 3.7 匹配响应体【JSON】
    在Android中实现区域截屏
    JVM 知识点全面梳理!
    开源.NetCore通用工具库Xmtool使用连载 - 图形验证码篇
    vue中请求接口
    ABAP MD04增强排除MRP元素
  • 原文地址:https://blog.csdn.net/Jackson_Mseven/article/details/134520080