Vu3 Computed 手写源码

104 阅读1分钟

禁止转载,侵权必究!

Computed 实现原理

接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 ref 对象。

  • 计算属性的 getter 只有当取值时才会执行。
  • 计算属性是具备缓存的,如果依赖的值不发生变化,不会重新执行 getter。dirty 变量来控制默认 true;
  • 计算属性也是一个 effect,内部也具备依赖收集的功能

computed.html script

import { reactive, effect, computed } from "./reactivity.esm.js";
const state = reactive({ flag: true, name: "jw", age: 30 });
const aliasName = computed(() => {
  console.log("computed-run");
  return "**" + state.name + "**";
});
const runner = effect(() => {
  console.log("effect-run");
  console.log(aliasName.value);
  console.log(aliasName.value);
  console.log(aliasName.value);
});
setTimeout(() => {
  state.name = "Mr Jiang";
}, 1000);

1.计算属性的实现

computed.ts

class ComputedRefImpl {
  public effect; // 计算属性是基于effect实现的
  public _value; // 缓存计算属性的执行结果
  public dep; // 记录依赖的effect
  public __v_isRef = true; // 计算属性是一个ref
  public _dirty = true; // 标识此computed依赖的值是否发生变化
  constructor(getter, public setter) {
    // 创建 ReactiveEffect 时,传入scheduler函数,稍后依赖的属性变化时调用此方法!
    this.effect = new ReactiveEffect(getter, () => {
      if (!this._dirty) {
        // 依赖的值变化更新dirty并触发更新
        this._dirty = true;
        triggerEffects(this.dep);
      }
    });
  }
  // 类的属性访问器 Object.defineProperty(实例,value,{get,set})
  get value() {
    // 取值的时候进行依赖收集
    if (activeEffect) {
      trackEffects(this.dep || (this.dep = new Set()));
    }
    if (this._dirty) {
      // 如果是脏值, 执行函数
      this._dirty = false;
      this._value = this.effect.run();
    }
    return this._value;
  }
  set value(newValue) {
    this.setter(newValue);
  }
}
const noop = () => {};
export function computed(getterOrOptions) {
  const onlyGetter = isFunction(getterOrOptions); // 传入的是函数就是getter;
  let getter;
  let setter;
  if (onlyGetter) {
    getter = getterOrOptions;
    setter = noop;
  } else {
    getter = getterOrOptions.get;
    setter = getterOrOptions.set || noop;
  }
  // 创建计算属性
  return new ComputedRefImpl(getter, setter);
}

2.收集依赖的 effect

effect.ts

export function trackEffects(dep) {
  // 收集dep 对应的effect
  let shouldTrack = !dep.has(activeEffect);
  if (shouldTrack) {
    dep.add(activeEffect);
    activeEffect.deps.push(dep);
  }
}

3.触发依赖的 effect 更新

effect.ts

export function triggerEffects(dep) {
  if (dep) {
     const effects = [...dep];
     effects.forEach((effect) => {
      if (effect !== activeEffect) {
          if (effect.scheduler) {
            // 如果有调度函数则执行调度函数
            effect.scheduler();
          } else {
            effect.run();
          }
       }
     });
  }
  
}