- REC , reactivity (响应式)
- track 跟踪、trigger 触发、effect副作用
reactivity
1.1 无响应式 ``` let price = 5; let quantity = 2; let total = price * quantity console.log(total) // 10
price = 20 console.log(total) // 10, 期望:40
1.2 简陋响应式
- 存储total计算式
- 改变price、quantity后再次运行total计算
function effect() { total = price * quantity; } effect();
1.3 dep存储effect, track管理dep, trigger触发响应。 监听单个属性, 单层依赖
let dep = new Set(); function track() { dep.add(effect) } function trigger() { dep.forEach(effect => effect()) }
track(); // save this code effect(); // run this effect trigger(); // run all the code I’ve saved
<a name="hLWhw"></a>
#### 1.4 深度响应
![image.png](https://cdn.nlark.com/yuque/0/2020/png/601538/1607225116834-0d377049-ac3d-4a19-8cc3-a5592c9855d6.png#align=left&display=inline&height=263&margin=%5Bobject%20Object%5D&name=image.png&originHeight=526&originWidth=1406&size=374414&status=done&style=none&width=703)
const depsMap = new Map(); function track(key) { let dep = depsMap.get(key); if (!dep) { dep = new Set() depsMap.set(key, dep) } dep.add(effect); } function trigger(key) { let dep = depsMap.get(key); if (dep) { dep.forEach((effect) => { effect(); }); } }
track(‘quantity’); effect(); trigger(‘quantity’);
<a name="Mkc9F"></a>
#### 1.5 多属性响应
![image.png](https://cdn.nlark.com/yuque/0/2020/png/601538/1607226126674-b5788c92-b205-4e6a-a4d9-11356719d2f7.png#align=left&display=inline&height=318&margin=%5Bobject%20Object%5D&name=image.png&originHeight=636&originWidth=1522&size=556381&status=done&style=none&width=761)<br />弱映射
const targetMap = new WeakMap(); targetMap.set(product, “example code to test”); console.log(targetMap.get(product)) // example code to test
```
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;
}
let dep = depsMap.get(key);
if (dep) {
dep.forEach((effect) => effect());
}
}
track(product, 'quantity');
effect();
trigger(product, 'quantity');
Proxy and Reflect
2.1 拦截GET和SET
- vue2: ES5 Object.property()
- vue3: ES6 Reflect + ES Proxy, ⚠️ 不支持IE
三种获取对象属性
console.log(product.price); // 5
console.log(product['price']); // 5
console.log(Reflect.get(product, 'price')); // 5
2.2 proxy
let proxiedproduct = new Proxy(product, {
get (target, key) {
console.log('get')
return target[key]
}
});
console.log(proxiedproduct.price);
2.3 Reflect
get(target, key, receiver) {
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
}
2.4 抽象为reactive函数
function reactive (target) {
const handler = {
get ,
set
};
return new Proxy(target, handler )
}
let product = reactive({ price: 5, quantity: 2 });
2.5 get时调用track, set时如果有变化调用trigger
get (target, key, receiver) {
let res = Reflect.get(target, key, receiver);
track(target, key)
return res;
},
set (target, key, value, receiver) {
let oldVal = target[key];
let res = Reflect.set(target, key, value, receiver)
if (oldVal !== res) {
trigger(target, key)
}
return res
}
activeEffect and ref
activeEffect变量(正在运行的主动效果)
优化: 访问时, 如果没有effect, 不调用track
function effect(eff) {
activeEffect = eff; // set this as the activeEffect
activeEffect(); // run it
activeEffect = null; // unset it
}
function track(key) {
if (activeEffect) {
// ...
dep.add(activeEffect);
}
}
ref
let salePrice = ref(0);
effect(() => {
total = salePrice.value * product.quantity;
});
effect(() => {
saleprice.value = product.price * 0.9;
});
- use Reactive
- 对象访问器(JS的计算器属性)
function ref(initialvalue) {
return reactive({ value: initialvalue });
}
function ref(raw) {
const r = {
get value() {
track(r, 'value');
return raw;
},
set value(newVal) {
raw = newVal;
trigger(r, 'value');
}
};
return r;
}
Computed and watch
计算属性
``` let total = computed(() => { return salePrice.value * product.quantity; });
function computed(getter) { let res = ref(); effect(() => (res.value = getter)); return res; }
<a name="8qW6s"></a>
#### vue2 限制- 无法监听新增属性
Vue.set(product, ‘name’, ‘Jack’)
<a name="oWv0w"></a>
#### 初步尝试vue3 API
<a name="Pmbil"></a>
#### 实现响应式基础:
- effect.ts 定义effect、track、trigger
- baseHandlers.ts 定义proxy 处理器(set、get)
- reactive.ts 定义reactive依赖创建ES6 Poxy
- ref.ts 定义ref借助对象访问器
- computed.ts 定义计算属性, 借助effect并返回一个ref
<a name="k6QcB"></a>
### 常见问题
1. track和trigger代替vue2中的depend和notify实现effect?
| | Save this code | Run all the code I've saved |
| --- | --- | --- |
| vue2 | depend() | notify() |
| vue3 | track() | trigger() |
- 功能相同, 名字不同。前者时动词, 依赖实例被依赖,通知订阅者,依赖关系。后者,跟踪和触发函数相互独立。跟踪依赖类, 而不是被依赖。
2. vue2 中响应式Dep是一个具有subscribers的类, 而vue3中的dep是一个简单的set
class Dep { constructor() { this.subscribers = []; // 1. 订阅者 } depend() { // 2. 依赖函数 if (target && !this.subscribers.includes(target)) { this.subscribers.push(target) } } notify() { // 3. 通知函数 this.subscribers.forEach(sub => sub()) } } // VS let Dep = new Set()
3. 如何在vue3中解决effect的储存?
4. 为什么以访问器的方式使用对象访问器属性?
function ref(intialvalue) { return reactive({ value: intialvalue }) } // VS function ref(raw) { const r = { get value() { track(r, ‘value’) }, set value(newVal) { raw = newVal trigger(r, ‘value’) } } }
5. 使用Reflect和Proxy实现属性响应式,还带来什么好处?
- 使用响应式转换, 具有惰性监听(当对象调用响应式函数时,才返回一个代理对象)
<a name="esQGs"></a>
### 源码解析
![](https://cdn.nlark.com/yuque/0/2020/jpeg/601538/1607958969787-22ba5e25-0fa8-4898-b38a-50e5b2dc960c.jpeg)<a name="joFZl"></a>
#### debugger hooks
const { reactive, watchEffect } = Vue
const state = reactive({ count: 1 })
watchEffect(() => { console.log(state.count) }, { onTrack(e) { console.log(e) // effect, key: “count”, target: { count: 1 }, type: “get” }, onTrigger(e) { console.log(e) // change state.count } // newValue: 2, oldValue: 1, oldTarget, type: “set” })
setTimeout(() => { state.count++; }, 3000)
渲染组件的钩子函数
const App = { renderTracked(e) {}, renderTriggered(e) {} } ```