[Vue 源码] Vue 3.2 - toRef | toRefs | proxyRefs 原理
运行结果
代码示例
<script src="./dist/reactivity.global.js"></script>
<body>
<div id="app"></div>
<script>
let { ref, effect, reactive, toRef } = VueReactivity
let obj = reactive({ flag: false })
let state = toRef(obj, "flag")
effect(() => {
app.innerHTML = `ref is ${state.value} `
})
setTimeout(() => {
state.value = true
}, 2000)
</script>
挂载阶段
第一:调用 toRef 函数,调用 new ObjectRefImpl 创建 objectRef 对象。
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K,
defaultValue?: T[K]
): ToRef<T[K]> {
const val = object[key]
return isRef(val)
? val
: (new ObjectRefImpl(object, key, defaultValue) as any)
}
第二:调用 ObjectRefImpl 的 constructor 方法。初始化 object, key 属性。返回 objectRef 对象。
constructor(
private readonly _object: T,
private readonly _key: K,
private readonly _defaultValue?: T[K]
) {}
第三:再 effect 函数 执行 effct.fn 函数,也就是 () => { state.value = true }
, 访问 state.value, 触发 objectRef 对象 value 属性的 get 操作。收集依赖到 obj 响应式对象的 flag 属性的依赖列表中去,返回属性值。
const targetMap = {
{flag :false}: {flag: [ReactiveEffect]}
}
get value() {
const val = this._object[this._key]
return val === undefined ? (this._defaultValue as T[K]) : val
}
第四:初次挂载完成,渲染完毕
更新阶段
执行第一句代码:
setTimeout(() => { state.value = true }, 2000)
触发 state objectRef 对象 value 属性的 setter 方法,实际上触发的是 reactive 响应式对象 obj 的 setter 操作。
set value(newVal) {
this._object[this._key] = newVal
}
再 setter 操作中,通过 Reflect.set 方法修改响应式对象的值,再通过 trigger 操作。触发 flag 属性的依赖列表。再次执行 ReactiveEffect 对象的 fn 属性,拿到最新值,进行渲染。
更新完毕
toRefs 原理
<script>
let { ref, effect, toRefs, reactive } = VueReactivity
let state = reactive({ num: 0, name: 'cyan' })
const { num, name } = toRefs(state)
effect(() => {
app.innerHTML = `ref is ${num.value} ${name.value}`
})
setInterval(() => {
num.value++
name.value = 'mike'
}, 2000)
</script>
就是将对象的每一个属性,都调用 toRef 函数,转成 objectRef 对象。这样就可以进行解构,拿到每个 objectRef 对象,且仍然具有响应式。
export function toRefs<T extends object>(object: T): ToRefs<T> {
if (__DEV__ && !isProxy(object)) {
console.warn(`toRefs() expects a reactive object but received a plain one.`)
}
const ret: any = isArray(object) ? new Array(object.length) : {}
for (const key in object) {
ret[key] = toRef(object, key)
}
return ret
}
proxyRefs 原理
let { ref, effect, toRefs, reactive, proxyRefs } = VueReactivity
let state = reactive({ num: 0, name: 'cyan' })
const p = proxyRefs(toRefs(state))
effect(() => {
app.innerHTML = `ref is ${p.num} ${p.name}`
})
setInterval(() => {
p.num++
p.name = 'mike'
}, 2000)
proxyRefs 原理就是将 ref 对象做了一层 proxy 代理,访问 p.num 实际上是访问 p.num.value, 触发的仍然是 objectRef 对象 的 getter。 赋值p.num = "",实际上是 p.num.value = "", 触发的仍然是 objectRef 对象的 settter。
export function proxyRefs<T extends object>(
objectWithRefs: T
): ShallowUnwrapRef<T> {
return isReactive(objectWithRefs)
? objectWithRefs
: new Proxy(objectWithRefs, shallowUnwrapHandlers)
}
const shallowUnwrapHandlers: ProxyHandler<any> = {
get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
set: (target, key, value, receiver) => {
const oldValue = target[key]
if (isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
} else {
return Reflect.set(target, key, value, receiver)
}
}
}
转载自:https://juejin.cn/post/7209967260897755194