前言
大家好,我是落叶小小少年,虽然比较菜,虽然才开始写作分享,我始终相信
- 核心demo更容易理解深的技术点
- 每一次基础的学习都是对知识的巩固
因为从年初就开始使用Vue3了,现在才来学习Vue3,但是也不算晚,学到就是赚到,知识无价,只要今天的知识比昨天多一点就是在丰富自己。那么我们就来学习下Vue3的响应式原理
Vue3的响应式原理
大家都知道Vue3使用的是Proxy进行代理的,这里我们先用Proxy实现一个最基础的响应式
我知道看vue3源码是一件比较头疼的事,所以在这里给大家提取出最简单的一个响应式demo,方便大家学习和理解
1. 响应式函数
先写一个创建reactive的函数,里面使用了Proxy进行target对象的代理
function createReactiveObject(target, baseHandler) {
const proxy = new Proxy(target, baseHandler);
return proxy;
}
2. Proxy的handler函数
接着我们实现对应的baseHandler函数,其中最主要的是get和set(当然还有has、ownKeys等就先不实现)
function get(target, key, recivier) {
const res = Reflect.get(target, key, recivier);
track(target, key);
// 深层响应式
return res !== null && typeof res === 'object' ? createReactiveObject(res) : res;
}
function set(target, key, value, recivier) {
const oldValue = target[key];
const res = Reflect.set(target, key, value, recivier);
if (!Object.is(value, oldValue)) {
trigger(target, key);
}
return res;
}
对应的get和set函数做的最主要的事情就是追踪和触发
追踪就是指当你去获取这个target上的key值时,能追踪到你使用了这个target上的key值,如果放到target对象来说,就是指你调用了target[key],比如console.log(target[key])
,这就是最简单的访问
触发就是指当你去修改这个值时,你已经追踪了这个值,那么你会有一个bucket去存这个副作用函数,当你修改对应key的值的时候,就需要从这个bucket里面找到对应的key的副作用函数,再去执行,达到响应式的效果
3. 如何设计副作用函数收集数据结构
那么我们应该怎么设计这个bucket才能正确存储上这个effect呢?
我们已经知道访问target[key]时要进行track,可以看出来和target和key有关系,但是一个target[key]肯定不止一个副作用函数使用,当然一个target上也有多个key可以收集副作用函数,所以我们需要这么设计:
targetMap是一个target对象到Map的映射,一个target上有多个key,所以key可以用Map存储
keyToDepMap是每个key对应的dep的映射,一个key可以对应多个dep(副作用函数收集)
4. 收集和触发
收集主要在track函数里面,触发则主要在trigger函数里面,也就是按照我们上面的结构在get时进行副作用收集和set时取出对应的副作用函数触发即可
const targetMap = new WeakMap();
function track(target, key) {
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
// 添加副作用函数
dep.add(effect);
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (dep) {
// 副作用函数触发
dep.forEach((fn) => fn());
}
}
最简单的方法就是直接写一个effect函数吧
上面看到我们实现了track和trigger,那么副作用函数从哪来呢?
const data = {
name: 'reactive'![在这里插入图片描述](https://img-blog.csdnimg.cn/9335d91de11b4df49f02de84e522c8a8.png)
}
const proxyData = createReactiveObject(data, {
get,
set,
});
function effect () {
// 直接访问了proxyData上的name属性
console.log(proxyData.name); // reactive
}
effect()
// 修改后会触发trigger里面收集到的函数
proxyData.name = 'ref' // ref
正如我们所预料的,当修改了name值后,重新触发了effect函数,也就是从targetMap中找到了对应的effect函数,重新输出了ref值。targetMap的值如下:
至此已经实现了一个最小最简单的响应式原理。
5. 解决effect名字写死问题
大家还记得上面我们在track函数里面收集effect函数时吗?
dep.add(effect)
这个add是固定添加effect函数的,这里就会有两个问题
- 只能使用effect这个副作用函数名
- 如果多个字段的副作用函数没办法区分开来
那么我们怎么解决这个问题呢?
很简单,就是用一个专门的name来收集effect函数,比如activeEffect,在执行某个副作用函数时就将activeEffect赋值乘这个副作用函数就行了
// 接上
let activeEffect
function effect(fn