- 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 深度响应
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 多属性响应<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); // 5console.log(product['price']); // 5console.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 activeEffectactiveEffect(); // run itactiveEffect = 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>### 源码解析<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) {} } ```
