上一篇文章已经实现了一个简单的reactive 实现Vue3中的reactive,这篇文章一起实现一下ref
我们先看一下vue官方的ref功能介绍
ref 对象是可更改的,也就是说你可以为
.value赋予新的值。它也是响应式的,即所有对.value的操作都将被追踪,并且写操作会触发与之相关的副作用。如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。这也意味着如果对象中包含了嵌套的 ref,它们将被深层地解包。
若要避免这种深层次的转换,请使用
shallowRef()来替代。
对于使用ref还是reactive,官方已经给出了答案:建议使用ref。其实ref在对于对象进行处理的时候,底层用到的还是reactive,不过ref可以实现对单个变量实现响应式,同时还可以在对象完全变换的时候进行重新出发依赖。
那我们先看一下vue是怎么实现ref的吧
先说一下如何调试vue源码。
- clone vue的core仓库,然后选择指定的版本,我这里选择的是3.2.37
- 依赖安装pnpm i
- 打包 pnpm build
- 然后在packages\vue\examples下建个html,引入dist打包的vue文件,写一些需要调试的代码,然后可以快乐的打断点了。
先写一段调试代码,如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ref</title>
</head>
<body>
<div id="app">
<p id="p1"></p>
<p id="p2"></p>
</div>
<script src="../../dist/vue.runtime.global.js"></script>
<script>
const { ref, effect } = Vue
const info = ref(12)
effect(() => {
document.querySelector('#p1').innerHTML = info.value
})
setTimeout(() => {
info.value = 22
}, 2000)
</script>
</body>
</html>
然后在ref处打上断点
然后进入ref内部看看是如何实现的
可以看到ref内部调用了createRef函数,函数接受两个参数一个是当前传入的值,一个是false。
继续进入createRef函数
这里,我们就不关注那些细节了。createRef函数内部返回一个RefImpl的实例,也是ref实现的核心代码
这里有个变量isShallow,在开头官方的一段话里就说了,如果不想实现深层的响应式,可以用shallowRef。这个isShallow就是为了实现shallowRef的。
变量dep大家应该很熟悉了,存储相关的依赖函数,不熟悉的可以去看我上一篇文章 点击查看如何实现Vue3中的reactive
变量 __v_isRef 判断当前是不是ref对象
变量 rawValue 存储对象的原始值
变量 value 存储当前的响应性值。
那ref是如何实现既可以响应式单个变量值,可以实现多个变量的值呢?
vue是这样实现的
- 如果传入的是对象,则将对象转换为reactive对象并赋值给value。
- 如果传入的是单个变量,则直接赋值给value
reactive已经实现了响应式,那如何实现单个变量的响应式呢?
在RefImpl里对于value劫持了get和set行为,当我们去调用ref.value的时候就会收集当前的依赖,给value赋值的时候就会将收集的依赖触发实现响应性。
大致的思维导图如下
那我们开始实现ref吧,首先建一个ref文件,导出一个ref函数
export function ref(value: any) {
return createRef(value, false)
}
接着实现createRef函数
function createRef(value: any, isShallow: boolean) {
if (isRef(value)) {
return value
}
return new RefImpl(value, isShallow)
}
接着实现RefImpl类
class RefImpl {
public __v_isShallow: boolean
public dep: undefined | Dep
public __v_isRef: boolean
public _value: any
constructor(value: any, isShallow: boolean) {
this.__v_isShallow = isShallow
this.dep = undefined
this.__v_isRef = isRef(value)
this._value = isShallow ? value : toReactive(value)
}
get value() {
console.log(this._value)
trackRefValue(this)
return this._value
}
set value(newValue) {
this._value = this.__v_isShallow ? newValue : toReactive(newValue)
triggerRefValue(this)
}
}
实现toReactive函数
function toReactive(value: any) {
return isObject(value) ? reactive(value) : value
}
实现trackRefValue依赖搜集
function trackRefValue(ref: RefImpl) {
if (!ref.dep) {
ref.dep = createDep()
}
if (activeEffect) {
ref.dep.add(activeEffect)
}
}
实现triggerRefValue依赖触发
function triggerRefValue(ref: RefImpl) {
if (ref.dep) {
triggerEffects(ref.dep)
}
}
在index.ts文件中引入ref函数并导出
export { effect } from './effect'
export { reactive } from './reactive'
export { ref } from './ref'
然后,我们进行打包,并测试上面写的代码,符合要求则实现了ref功能。具体代码请移步到我的github
github.com/Wkb2317/my-…
总结
有了实现reactive的经验后 (移步文章实现Vue3中的reactive),我们实现ref就很简单了。对于复杂对象底层用reactive进行处理,并将代理对象复制给ref的value字段;对于单一变量直接复制给value字段(这也是为啥ref要通过.value进行获取数据)。然后对于操作value的get和set行为进行拦截,和proxy类似,在get时进行依赖收集,在set进行依赖触发。因为是修改的value下的属性,所以不管是复杂对象还是单一变量都可以触发响应式。
完整代码: github.com/Wkb2317/my-…,欢迎start