Vue3源码笔记-响应式
近期有空看了下 vue3 源码和相关书籍,同时将笔记同步到了博客。后面忘了可以再看看。为了理清核心主线逻辑,代码均移除了次要&边界逻辑。
一. 基本实现
设定基本场景,单击 add 按钮后,counter.num
值变化,触发 effect 内匿名函数执行,达到响应式效果;
<button onclick="add()">add</button>
<div id="value"></div>
<script>
// 1.获取响应式对象
const counter = reactive({ num: 0 });
// 2.副作用函数
effect(() => {
console.log("effect", counter);
document.getElementById("value").innerText = counter.num;
});
// 3.触发
let i = 1;
window.add = () => {
counter.num = i++;
};
</script>
在线演示
See the Pen vue-reactivity-1 by 唐鸽 (@tggcs) on CodePen.
源码拆分
- 定义当前活跃副作用函数、依赖收集桶;
// 全局变量
const targetMap = new WeakMap();
let activeEffect;
- 实现依赖收集、触发,即
track
,trigger
;
vue3 用 proxy 代理,最基本的就是在 get,set 内设置收集和触发的机制;
// 实现响应式对象,拦截get,set,加入track,trigger
function track(target, type, 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(activeEffect);
console.log("track", targetMap);
}
function trigger(target, type, key, newValue) {
const depsMap = targetMap.get(target);
console.log("\n trigger", target, type, key, newValue);
depsMap.get(key).forEach((effect) => {
effect.run();
});
}
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
track(target, "get", key);
return res;
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver);
trigger(target, "set", key, value);
return res;
},
});
}
- 绑定&触发副作用函数;
class ReactiveEffect {
fn;
constructor(fn) {
this.fn = fn;
}
run() {
activeEffect = this;
return this.fn();
}
}
function effect(fn) {
const _effect = new ReactiveEffect(fn);
_effect.run();
}
bucket 桶结构
依赖收集桶结构&deps 绑定
二.嵌套的 effect
设定基本场景,单击 add 按钮后,counter.num1
值变化, 但打印如下;
<button onclick="add()">add</button>
<script>
const counter = reactive({ num1: 0, num2: 0 });
effect(() => {
effect(() => {
console.log(`num2:${counter.num2}`);
});
console.log(`num1:${counter.num1}`);
});
console.log(targetMap);
let i = 1;
window.add = () => {
counter.num1 = i++;
};
</script>
num2: 0;
parent 变量解决嵌套
没有见到num1:1
的打印,是因为依赖收集时,activeEffect
是内层副作用函数(且由 set 做了去重),按 vue3 设计,加一个 parent 变量即可;
...
run() {
try {
// parent记录下嵌套的父级activeEffect
this.parent = activeEffect;
activeEffect = this;
return this.fn();
} finally {
activeEffect = this.parent;
this.parent = undefined;
}
}
...
在线演示
See the Pen vue-reactivity-2 by 唐鸽 (@tggcs) on CodePen.
三.依赖清理
改用二进制标记位的方式,较之前全部清除再收集的方式有更良好的性能(仅删除不需要的副作用函数);
设定基本场景,单击 add 按钮后,counter.visible
值切换,visible===false
时,targetMap 桶内counter.num2
的 dep 集合应该清空;
<button onclick="toggle()">toggle</button>
<script>
const counter = reactive({ visible: true, num2: 0 });
effect(() => {
if (counter.visible) {
console.log("num2", counter.num2);
}
});
let visible = true;
function toggle() {
visible = !visible;
counter.visible = visible;
}
</script>
源码拆分
- 添加三个变量,用于控制
let effectTrackDepth = 0; // 当前所在副作用函数层级
let trackOpBit = 1; // 所在层级的二进制标记
const maxMarkerBits = 30; // 设定effect最大嵌套30层
- 通过
已被收集
和新的依赖
两个标记来筛选剔除无效副作用函数
副作用函数执行前,执行initDepMarkers
,将存在deps
打上已被收集
标记;副作用函数执行前,执行finalizeDepMarkers
,将effect
是 已被收集
&& 非新的依赖
删除掉;
initDepMarkers() {
...
deps[i].w |= trackOpBit // set was tracked
}
finalizeDepMarkers() {
...
if (wasTracked(dep) && !newTracked(dep)) {
dep.delete(effect)
}
}
run() {
try {
...
if (effectTrackDepth <= maxMarkerBits) {
initDepMarkers(this)
}
return this.fn();
} finally {
if (effectTrackDepth <= maxMarkerBits) {
finalizeDepMarkers(this)
}
...
}
}
See the Pen vue-reactivity-2 by 唐鸽 (@tggcs) on CodePen.
- 位操作
参考 权限设计应用
四.Ref
为了实现原始值的响应式,vue3提供了 ref api; 设定如下场景
const a = ref(1)
effect(() => {
console.log('effect', a.value)
})
a.value = 2
源码拆分
通过返回新实例,并劫持其value属性的getter&setter即可;并加入tracker\&trigger;
function ref(value) {
return new RefImpl(value)
}
class RefImpl {
_value
constructor(value) {
this._value = (value)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
this._value = newVal
triggerRefValue(this, newVal)
}
}
在线演示
See the Pen vue-reactivity-3 by 唐鸽 (@tggcs) on CodePen.
五.readonly & shallowReadonly
六.shallowReactive
七.computed
八.watch