reactive原理
INFO
首先要使用 reactive
属性,必须要在 effect
函数内部, 编译后每一个组件都是由一个effect
包裹
使用
本质是发布订阅模式当 reactive
的属性发生变化时,要通知所有的观察者,即 effect
内部回调函数要重新执行
const data = { name: 'jw', age: 30, flag: true }
const state = reactive(data);
effect(()=>{
app1.innerHTML = state.flag ? state.name : state.age
})
setTimeout(() => {
state.flag = false; // 会显示age
}, 1000)
effect
effect 接受一个函数,函数会自动执行一次
如果在函数内部使用了 reactive
对象,当 reactive
的属性发生变化时,会再次自动执行该函数
所以要把 effect
与 reactive
属性关联起来
定义 全局变量 activeEffect
不断的指向执行的 effect
,effect
有可能是嵌套,所以要记录父级effect
// 全局变量
let activeEffect = undefined;
class ReactiveEffect{
constructor(public fn) {}
parent = undefined;
deps = []; // effect中要记录哪些属性是在effect中调用的
run(){
// 当运行的时候 我们需要将属性和对应的effect关联起来
// 利用js是单线程的特性,先放在全局,在取值
try {
this.parent = activeEffect;
// 全局变量指向 当前effect实例
activeEffect = this;
cleanupEffect(this);
return this.fn(); // 触发属性的get
} finally {
activeEffect = this.parent;
this.parent = undefined;
}
}
}
function effect(fn) {
// 将用户的函数,拿到变成一个响应式的函数
const _effect = new ReactiveEffect(fn);
// 默认让用户的函数执行一次
_effect.run();
}
每次执行 effect
的时候,都会创建一个 ReactiveEffect
实例,并且 activeEffect
指向此实例
当执行 _effect.run()
时,会执行 fn
函数
reactive 原理
在 reactive
中,使用 Proxy
进行代理访问源对象的访问
function reactive(target){
// 必须保证是一个对象
if (!isObject(target)) {
return target;
}
const proxy = new Proxy(target, mutableHandlers);
return proxy
}
其中在 mutableHandlers
会使用 get/set
访问器属性代理访问源对象
依赖收集 get / set
const mutableHandlers = {
get(target, key, recevier) {
track(target, key);
return Reflect.get(target, key, recevier);
}
set(target, key, value, recevier) {
let oldValue = target[key];
let flag = Reflect.set(target, key, value, recevier);
if (value !== oldValue) {
trigger(target, key, value, oldValue);
}
return flag;
},
}
get触发 track
当触发 reactive
的 get
访问器时,会执行 track
方法,收集依赖
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()));
}
// 收集全局变量 activeEffect
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
使用 weakMap
收集 target
,在 target
上的 key
对应的 set
中添加 activeEffect
,同时 activeEffect.deps
也在收集对应的 dep
target
与 activeEffect
形成了相互依赖,target 对应的 set 收集 activeEffect
,activeEffect
的 deps
收集 dep
set触发 trigger
// { name: 'jw', age: 30 } -> {name => [effect,effect]}
function trigger(target, key) {
// 找到effect执行即可
const depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
let effects = depsMap.get(key);
if (effects) {
effects = [...effects];
effects.forEach((effect) => {
if (activeEffect !== effect) {
effect.run();
}
});
}
}
当触发 reactive
的 set
访问器时,会执行 trigger
方法,触发依赖的 effect
执行 effect.run
方法,会再次执行 fn
小技巧
如何避免重复 reactive
当 reactive
执行时,会判断 target
是否已经 reactive
过,如果已经 reactive
过,不再执行 reactive
let x = { name: 'zs' }
let y = reactive(x)
let r = reactive(y)
console.log(y == r) // true
代码如下:
enum ReactiveFlags {
"IS_REACTIVE" = "__v_isReactive",
}
function reactive(target){
if (target[ReactiveFlags.IS_REACTIVE]) {
return target;
}
// ...
}
const proxy = new Proxy(target, {
get(target, key, recevier) {
if (key === ReactiveFlags.IS_REACTIVE) {
return true;
}
}
});
- 第一次是普通对象,没有
ReactiveFlags.IS_REACTIVE
,所以是 false - 第二次再去
reactive
,传入的是proxy
过的对象,所以会触发get
访问器,返回true
避免对同一个对象 reactive
两次
let x = { name: 'zs' }
let y = reactive(x)
let r = reactive(x)
console.log(y == r) // true
const reactiveMap = new WeakMap(); // 防止内存泄露的
// 防止同一个对象被代理两次,返回的永远是同一个代理对象
let exitstingProxy = reactiveMap.get(target);
if (exitstingProxy) {
return exitstingProxy;
}
// 返回的是代理对象
const proxy = new Proxy(target, mutableHandlers);
reactiveMap.set(target, proxy);
全局属性 activeEffect
引用变化问题
更改 activeEffect
引用,并不会影响dep
已经存储 activeEffect
的值
因为 push
的是一个引用,当 a
重新赋值时,a
的引用地址发生了变化,但是存储的引用地址还是原来的
let a = null;
let o = {
c: []
}
a = { name: "zs" }
o.c.push(a)
a = { name: "lisi" }
o.c.push(a)
console.log(o.c) //[{name:'zs'},{name:'lisi'}]