摘要
在页面编写中,之前都是使用reactive来包裹对象,这样对象属性的值改变,其对应的effect包裹渲染动作就会被触发。并且通常有函数解构的操作,例如let people = reactive({name:"张三",age:24}); let {name,age} = people;
。如果这么结构操作的话,name和age就会变成普通的变量,那么如果在effect中使用的话,就算改变了值也不会触发回调函数。那么为了解决例如此类的需求,vue3提供了ref、toRef、toRefs
。
官方中的写法探究
引入官方的包,尝试了几种简单的写法。
<!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会被传入基础数据类型和引用型数据。所以方法要判断一下数据类型。
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中分离的依赖收集函数和更新触发的函数。
......
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提供的函数。
......
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对象使用更加方便了。
......
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的指向问题。第三篇文章分析了这个问题。
评论区