• 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. 1.2 简陋响应式
  2. - 存储total计算式
  3. - 改变pricequantity后再次运行total计算

function effect() { total = price * quantity; } effect();

  1. 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

  1. <a name="hLWhw"></a>
  2. #### 1.4 深度响应
  3. ![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’);

  1. <a name="Mkc9F"></a>
  2. #### 1.5 多属性响应
  3. ![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

  1. ```
  2. const targetMap = new WeakMap();
  3. function track(target, key) {
  4. let depsMap = targetMap.get(target);
  5. if (!depsMap) {
  6. targetMap.set(target, (depsMap = new Map()));
  7. }
  8. let dep = depsMap.get(key);
  9. if (!dep) {
  10. depsMap.set(key, (dep = new Set()));
  11. }
  12. dep.add(effect);
  13. }
  14. function trigger(target, key) {
  15. const depsMap = targetMap.get(target);
  16. if (!depsMap) {
  17. return;
  18. }
  19. let dep = depsMap.get(key);
  20. if (dep) {
  21. dep.forEach((effect) => effect());
  22. }
  23. }
  24. track(product, 'quantity');
  25. effect();
  26. trigger(product, 'quantity');

Proxy and Reflect

2.1 拦截GET和SET

  • vue2: ES5 Object.property()
  • vue3: ES6 Reflect + ES Proxy, ⚠️ 不支持IE

三种获取对象属性

  1. console.log(product.price); // 5
  2. console.log(product['price']); // 5
  3. console.log(Reflect.get(product, 'price')); // 5

2.2 proxy

  1. let proxiedproduct = new Proxy(product, {
  2. get (target, key) {
  3. console.log('get')
  4. return target[key]
  5. }
  6. });
  7. console.log(proxiedproduct.price);

2.3 Reflect

  1. get(target, key, receiver) {
  2. return Reflect.get(target, key, receiver);
  3. },
  4. set(target, key, value, receiver) {
  5. return Reflect.set(target, key, value, receiver);
  6. }

2.4 抽象为reactive函数

  1. function reactive (target) {
  2. const handler = {
  3. get ,
  4. set
  5. };
  6. return new Proxy(target, handler )
  7. }
  8. let product = reactive({ price: 5, quantity: 2 });

2.5 get时调用track, set时如果有变化调用trigger

  1. get (target, key, receiver) {
  2. let res = Reflect.get(target, key, receiver);
  3. track(target, key)
  4. return res;
  5. },
  6. set (target, key, value, receiver) {
  7. let oldVal = target[key];
  8. let res = Reflect.set(target, key, value, receiver)
  9. if (oldVal !== res) {
  10. trigger(target, key)
  11. }
  12. return res
  13. }

activeEffect and ref

activeEffect变量(正在运行的主动效果)

优化: 访问时, 如果没有effect, 不调用track

  1. function effect(eff) {
  2. activeEffect = eff; // set this as the activeEffect
  3. activeEffect(); // run it
  4. activeEffect = null; // unset it
  5. }
  6. function track(key) {
  7. if (activeEffect) {
  8. // ...
  9. dep.add(activeEffect);
  10. }
  11. }

ref

  1. let salePrice = ref(0);
  2. effect(() => {
  3. total = salePrice.value * product.quantity;
  4. });
  5. effect(() => {
  6. saleprice.value = product.price * 0.9;
  7. });
  • use Reactive
  • 对象访问器(JS的计算器属性)
    1. function ref(initialvalue) {
    2. return reactive({ value: initialvalue });
    3. }
    4. function ref(raw) {
    5. const r = {
    6. get value() {
    7. track(r, 'value');
    8. return raw;
    9. },
    10. set value(newVal) {
    11. raw = newVal;
    12. trigger(r, 'value');
    13. }
    14. };
    15. return r;
    16. }

    Computed and watch

    计算属性

    ``` let total = computed(() => { return salePrice.value * product.quantity; });

function computed(getter) { let res = ref(); effect(() => (res.value = getter)); return res; }

  1. <a name="8qW6s"></a>
  2. #### vue2 限制- 无法监听新增属性

Vue.set(product, ‘name’, ‘Jack’)

  1. <a name="oWv0w"></a>
  2. #### 初步尝试vue3 API
  3. <a name="Pmbil"></a>
  4. #### 实现响应式基础:
  5. - effect.ts 定义effect、track、trigger
  6. - baseHandlers.ts 定义proxy 处理器(set、get)
  7. - reactive.ts 定义reactive依赖创建ES6 Poxy
  8. - ref.ts 定义ref借助对象访问器
  9. - computed.ts 定义计算属性, 借助effect并返回一个ref
  10. <a name="k6QcB"></a>
  11. ### 常见问题
  12. 1. track和trigger代替vue2中的depend和notify实现effect?
  13. | | Save this code | Run all the code I've saved |
  14. | --- | --- | --- |
  15. | vue2 | depend() | notify() |
  16. | vue3 | track() | trigger() |
  17. - 功能相同, 名字不同。前者时动词, 依赖实例被依赖,通知订阅者,依赖关系。后者,跟踪和触发函数相互独立。跟踪依赖类, 而不是被依赖。
  18. 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()

  1. 3. 如何在vue3中解决effect的储存?
  2. 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’) } } }

  1. 5. 使用ReflectProxy实现属性响应式,还带来什么好处?
  2. - 使用响应式转换, 具有惰性监听(当对象调用响应式函数时,才返回一个代理对象)
  3. <a name="esQGs"></a>
  4. ### 源码解析
  5. ![](https://cdn.nlark.com/yuque/0/2020/jpeg/601538/1607958969787-22ba5e25-0fa8-4898-b38a-50e5b2dc960c.jpeg)<a name="joFZl"></a>
  6. #### 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)

  1. 渲染组件的钩子函数

const App = { renderTracked(e) {}, renderTriggered(e) {} } ```