摘要
在页面编写中,之前都是使用 reactive 来包裹对象,这样对象属性的值改变,其对应的 effect 包裹渲染动作就会被触发。并且通常有函数解构的操作,例如let people = reactive({name:"张三",age:24}); let {name,age} = people;
。如果这么结构操作的话,name 和 age 就会变成普通的变量,那么如果在 effect 中使用的话,就算改变了值也不会触发回调函数。那么为了解决例如此类的需求,vue3 提供了ref、toRef、toRefs
。
官方中的写法探究
引入官方的包,尝试了几种简单的写法。
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
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div class="" id="app"></div> <script src="./dist/reactivity.global.js"></script> <script> const { reactive, effect,toRef,ref,toRefs,proxyRefs } = VueReactivity // let people = reactive({name:"张三",age:99}) let name = ref("张三") let age = ref(23) let people = proxyRefs({name,age}) // let {name,age} = toRefs(people) effect(() => { app.innerHTML = `${people.name}今年${people.age}岁` })
setTimeout(() => { people.name = "利斯" },1000) </script> </body> </html>
|
可以用 Ref 包裹基础数据类型,也可以包裹引用型数据。让数据变成响应式数据,可以被 effect 收集,然后更新数据触发回调函数。
编写 Ref
首先开始编写 ref.ts,ref 会被传入基础数据类型和引用型数据。所以方法要判断一下数据类型。
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
| import { isArray, isObject } from "@vue/shared"; import { trackEffect, triggerEffect } from "./effect"; import { reactive } from "./reactive"; function toReactive(value) { return isObject(value) ? reactive(value) : value; }
class RefImpl { public _value; constructor(public rawValue) { this._value = toReactive(rawValue); }
get value() { return this._value; }
set value(newValue) { if (newValue !== this.rawValue) { this._value = toReactive(newValue); this.rawValue = newValue; } } }
function ref(value) { return new RefImpl(value); }
|
上面是一个初步的对象包裹,但是还没加入依赖收集。现在加入****源码学习-7****中分离的依赖收集函数和更新触发的函数。
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
| ...... import { isArray, isObject } from '@vue/shared' import { trackEffect, triggerEffect } from './effect' import { reactive } from './reactive' function toReactive(value){ return isObject(value) ? reactive(value) : value }
class RefImpl{ public _value public dep = new Set() public __v_isRef = true constructor(public rawValue){ this._value = toReactive(rawValue) }
get value(){ trackEffect(this.dep) return this._value; }
set value(newValue){ if(newValue !== this.rawValue){ this._value = toReactive(newValue); this.rawValue = newValue; triggetEffect(this.dep) } } }
function ref(value){ return new RefImpl(value) }
|
这样就完成了 Ref 的基本功能。现在有了包裹对象,那么就有第二中需求,结构这种需求。toRefs 和 toRef 就是 vue3 提供的函数。
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
| ...... class ObjectRefImpl { constructor(public object, public key) {}
get value() { return this.object[this.key] }
set value(newValue) { this.object[this.key] = newValue } }
export function toRef(object, key) { return new ObjectRefImpl(object, key) }
export function toRefs(object) { const result = isArray(object) ? new Array(object.length) : {}
for (let key in object) { result[key] = toRef(object, key) }
return result }
|
toRefs 就比较简单就是单个 toRef 的单个版本实现,所以基于 toRefs 就基于 toRef 实现就行。
另外可以观察到 vue3 中 Ref 包裹的对象在 template 中使用发现不需要xx.value
,这是 vue3 官方做的一个优化,实际就像是 ref 包裹的反向过程。
编写 proxyRefs
上面知道了 vue3 提供了一个 proxyRefs 这样的函数,来使得 ref 对象使用更加方便了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ...... export function proxyRefs(object) { return new Proxy(object, { get(target, key, recevier) { let r = Reflect.get(target, key, recevier) return r.__v_isRef ? r.value : r },
set(target, key, value, receiver) { let oldValue = target[key] if (oldValue.__v_isRef) { oldValue.value = value return true } else { return Reflect.set(target, key, value, receiver) } }, }) }
|
template 模板上通常都是 get 数据那么就是代理对象反射这个.value
这样,为啥要用 Reflect 来反射,是因为 this 的指向问题。第三篇文章分析了这个问题。
1
| git:[@github/MicroMatrixOrg/vue3-plan/tree/ref]
|