前言
尝试编写 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
}
- 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来对比
}
-
这是使用宽松的比较方式,感觉和鸭式辩型(像鸭子一样走路并且嘎嘎叫的就叫鸭子)类似.
a. looseEqual(1, '1') // true
b. looseEqual(new Set(), new Set(['1'])) // true
c. looseEqual(new Map([['name', 'xzw']]), new Map()) // true -
上面这些都不符合我们预期的结果,所以使用v-model绑定时,进行回显的时候要特别注意.
可以查看小栗子中的第二个例子,那样使用就是有问题的.
总结
vModelRadio 的实现比较简单.
关于回显时可能需要注意下,当发现回显出问题时,可以查看looseEqual.ts
代码.
下篇: Vue3疑问系列(7) — v-model(vModelSelect)指令是如何工作的?
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!