今天看啥  ›  专栏  ›  徐志伟酱

Vue3疑问系列(6) — v-model(vModelRadio)指令是如何工作的?

徐志伟酱  · 掘金  ·  · 2021-03-02 15:55
阅读 56

Vue3疑问系列(6) — v-model(vModelRadio)指令是如何工作的?

前言

本文来聊聊 “v-model 使用在 input 上且 type 类型为 radio,其内部又是如何工作的”

看这篇文章前,一定要把官网中的 表单输入绑定[单选框 (Radio)例子看懂] 官网教程或者小栗子

本文会重点聊聊looseEqual.ts,这个明白后,有些使用姿势在使用时,就会特别注意了

尝试编写 vModelRadio 指令对象

这次就不编写 vModelRadio 指令对象了,因为比较简单.

小栗子

小栗子就不贴代码了,点我去看看

看完使用姿势接下来看看内部是如何使用的.

vModelRadio内部实现

vModelRadio源码 runtime-dom/src/directives/vModel.ts

export const vModelRadio: ModelDirective<HTMLInputElement> = {
  created(el, { value }, vnode) {
    el.checked = looseEqual(value, vnode.props!.value) // eg: `<input type="radio" v-model="str" value="foo" />` value就是str变量的值, vnode.props!.value就是foo这个值
    el._assign = getModelAssigner(vnode) // 拿到 onUpdate:modelValue 函数
    addEventListener(el, 'change', () => { // 给el 监听 change事件
      el._assign(getValue(el)) // eg: `<input type="radio" v-model="str" value="foo" />` 给str赋值
    })
  },
  beforeUpdate(el, { value, oldValue }, vnode) {
    el._assign = getModelAssigner(vnode) // 每次更新时都会获取最新的 onUpdate:modelValue 函数
    if (value !== oldValue) { // 新老值不相等
      el.checked = looseEqual(value, vnode.props!.value) // 根据looseEqual的结果进行回显
    }
  }
}

// retrieve raw value set via :value bindings
function getValue(el: HTMLOptionElement | HTMLInputElement) {
  return '_value' in el ? (el as any)._value : el.value
}
复制代码
  1. vModelRadio实现原理:
  • eg: <input type="radio" value="foo" v-model="str">
    a. 绑定值: str
    b. vnode.props!.value : 'foo'
    c. el: input 这个dom元素
    d. onUpdate:modelValue: $event => (str = $event) 这是模板编译生成的

  • created钩子中:
    a. 根据绑定值vnode.props!.value值的比较结果给el的checked赋值(初始化)
    b. 注册change事件,事件回调触发后调用onUpdate:modelValue(el.value) 就是el.value的值 赋值给 绑定值

  • beforeUpdate钩子中:
    a. 当新老值不相等时,根据 looseEqual(value, vnode.props!.value)的结果 赋值给 el.checked

  • 这样双向绑定就完成了

looseEqual.ts源码

import { isArray, isDate, isObject } from './'

function looseCompareArrays(a: any[], b: any[]) { // 数组a 和 数组b会否相等
  if (a.length !== b.length) return false // 长度不一样则不相等
  let equal = true
  for (let i = 0; equal && i < a.length; i++) {
    equal = looseEqual(a[i], b[i]) // 发现有a[i] 和 b[i]不相等,则跳出循环
  }
  return equal // 返回对比结果
}

export function looseEqual(a: any, b: any): boolean { // a 和 b 是否相等
  if (a === b) return true // 如果强等则返回true
  let aValidType = isDate(a) // a 是否 日期类型 根据 a instanceof Date来判断
  let bValidType = isDate(b) // b 是否 日期类型
  if (aValidType || bValidType) {
    return aValidType && bValidType ? a.getTime() === b.getTime() : false // 如果都是日期类型根据时间戳判断
  }
  aValidType = isArray(a) // a 是否 数组 根据 Array.isArray 来判断
  bValidType = isArray(b) // b 是否 数组
  if (aValidType || bValidType) {
    return aValidType && bValidType ? looseCompareArrays(a, b) : false // 调用looseCompareArrays比较
  }
  aValidType = isObject(a) // a 是否 对象 根据  !== null && typeof a === 'object' 来判断
  bValidType = isObject(b) // b 是否 对象
  if (aValidType || bValidType) {
    /* istanbul ignore if: this if will probably never be called */
    if (!aValidType || !bValidType) { // eg: a是null,b是非null的对象
      return false
    }
    const aKeysCount = Object.keys(a).length // a对象key的个数
    const bKeysCount = Object.keys(b).length // b对象key的个数
    if (aKeysCount !== bKeysCount) { // 个数是否相等
      return false // 不相等肯定不一样
    }
    for (const key in a) { // 遍历
      const aHasKey = a.hasOwnProperty(key) // a是否含有 key
      const bHasKey = b.hasOwnProperty(key) // b是否含有 key
      if (
        (aHasKey && !bHasKey) || // a有且b没有
        (!aHasKey && bHasKey) || // a没有且b有
        !looseEqual(a[key], b[key]) // a[key] 和 b[key] 不相等
      ) { // 上面3中情况任意一种,都表示 a 和 b 不相等
        return false
      }
    }
  }
  return String(a) === String(b) // 转成字符串比较
}

export function looseIndexOf(arr: any[], val: any): number {
  return arr.findIndex(item => looseEqual(item, val)) // 根据looseEqual来对比
}
复制代码
  1. 这是使用宽松的比较方式,感觉和鸭式辩型(像鸭子一样走路并且嘎嘎叫的就叫鸭子)类似.
    a. looseEqual(1, '1') // true
    b. looseEqual(new Set(), new Set(['1'])) // true
    c. looseEqual(new Map([['name', 'xzw']]), new Map()) // true

  2. 上面这些都不符合我们预期的结果,所以使用v-model绑定时,进行回显的时候要特别注意.
    可以查看小栗子中的第二个例子,那样使用就是有问题的.

总结

vModelRadio 的实现比较简单.
关于回显时可能需要注意下,当发现回显出问题时,可以查看looseEqual.ts代码.

下篇: Vue3疑问系列(7) — v-model(vModelSelect)指令是如何工作的?




原文地址:访问原文地址
快照地址: 访问文章快照