H5W3
当前位置:H5W3 > JavaScript > Vue相关 > 正文

Vue3 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)指令是如何工作的?

本文地址:H5W3 » Vue3 v-model(vModelRadio)指令是怎么工作的?

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址