likes
comments
collection
share

vue3-computed的简易实现(3)

作者站长头像
站长
· 阅读数 10

基本用法

官方用法:接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 getset 函数的对象来创建一个可写的 ref 对象。

根据上面可以知道, computed函数接受两种方式的传值, 下面来简单实现一下

// shared文件里面添加一下isFunction
export function isFunction(value: unknown): value is Record<any, any> {
  return typeof value === "function";
}

computed函数实现

在 package/reactivity/src 新建 computed.ts 文件

import { isFunction } from "@vue/shared";

export function computed(getterOrOptions) {
  let onlyGetter = isFunction(getterOrOptions);
  let getter;
  let setter;
  // 如果只有getter那么默认就不给setter了
  if (onlyGetter) {
    getter = getterOrOptions;
    setter = () => {
      console.warn("no set");
    };
  } else {
    getter = getterOrOptions.get;
    setter = getterOrOptions.set;
  }

  // 再降参数传给ComputedRefImpl类
  return new ComputedRefImpl(getter, setter);
}

ComputedRefImpl类实现

import { ReactiveEffect } from "./effect";

class ComputedRefImpl {
  public effect;
  public _dirty = true; // 用来检测值是否需要更新
  public _value;
  public dep = new Set(); // 记录自身值依赖
  constructor(public getter, public setter) {
    // 将getter函数放到ReactiveEffect中, 里面的依赖就会被当前的effect收集起来
    this.effect = new ReactiveEffect(getter, () => {});
  }
  // 这里利用的是class中的getter和setter
  get value() {
    // 如果值有变动才需要进行取值, 不然就直接返回原值
    if (this._dirty) {
      this._dirty = false;
      this._value = this.effect.run();
    }
    return this._value;
  }
  set value(newValue) {
    this.setter(newValue);
  }
}

下面就是考虑在依赖变动的时候如何触发effect, 流向是 值变动 -> computedEffect -> 外层effect, 因为自身只有value属性的原因, 我们只需要将effect收集到我们的dep属性中,将来直接触发就可以了,我们改造一下 track 和 trigger 函数

const targetMap = new WeakMap();
export function track(target, type, key) {
  if (activeEffect) {
    // 看看是否已经存在依赖
    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()));
    }
    trackEffects(dep); // 调用函数
  }
}

export function trackEffects(dep) {
  if (activeEffect) {
    let shouldTrack = !dep.has(activeEffect);
    if (shouldTrack) {
      dep.add(activeEffect);
      activeEffect.deps.push(dep); // 保存dep, 方便后续处理
    }
  }
}

export function trigger(target, type, key?, newValue?, oldValue?) {
  const depsMap = targetMap.get(target); // 获取对应的映射表
  if (!depsMap) {
    return;
  }
  let effects = depsMap.get(key);
  if (effects) {
    triggerEffects(effects); // 调用函数
  }
}

export function triggerEffects(effects) {
  effects = new Set(effects);
  effects.forEach((effect) => {
    if (effect !== activeEffect) {
      if (effect.scheduler) {
        effect.scheduler();
      } else {
        effect.run();
      }
    }
  });
}

在获取value值的时候就先进行依赖收集, 一旦 new ReactiveEffect 的 scheduler 函数执行, 证明computed函数中的值发生了变动, 那么就应该要重新取值, 并且更新外层对 computed属性的 effect

  import { ReactiveEffect, trackEffects, triggerEffects } from "./effect";
  class ComputedRefImpl {
      constructor(public getter, public setter) {
        // 将getter函数放到ReactiveEffect中, 里面的依赖就会被当前的effect收集起来
        this.effect = new ReactiveEffect(getter, () => {
          if (!this._dirty) {
            this._dirty = true; // 证明值重新设置过, 下次需要获取新值
            triggerEffects(this.dep);
          }
        });
      }
      // 这里利用的是class中的getter和setter
      get value() {
        // 进行依赖收集
        trackEffects(this.dep);
        if (this._dirty) {
          this._dirty = false;
          this._value = this.effect.run();
        }
        return this._value;
      }
  }

总体的执行流程

vue3-computed的简易实现(3)