您的当前位置:首页正文

vue3源码(三)computed

2024-12-02 来源:个人技术集锦

1.功能

接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 ref 对象。
默认不执行,在取值时执行,具有缓存功能,数据不变多次取值只触发一次取值计算。

      const state = reactive({ name: "orange" });
      let aliasName = computed(() => {
        console.log("执行计算方法");
        return "_" + state.name;
      });
      console.log("开始取值111");
      console.log(aliasName.value);
      console.log(aliasName.value);
      console.log(aliasName.value);
      state.name = "apple";
      console.log(aliasName.value);

2.原理:脏值检测机制**

计算属性本质是一个effect,内部保存一个变量dirty,用来判断是否需要重新执行。默认值为true,需要执行,每次执行effect后把dirty改为false,缓存执行结果,后续取值判断为false,直接取缓存的值,依赖的属性发生变化后,dirty改为true

3.实现 (vue 3.4版本)

console.log(aliasName)

3.1修改effect

在effect中增加一个标识_dirtyLevel,初始值为DirtyLevels.Dirty,在run()方法中更新为DirtyLevels.NoDirty,并提供出getset方法

export const enum DirtyLevels {
  Dirty = 4, // 脏值意味着要运行计算属性
  NoDirty = 0, // 不脏就用上次的取值结果
}

export class ReactiveEffect {
  ...
  public _dirtyLevel = DirtyLevels.Dirty; // 默认是脏的,计算属性运行过一次就不脏
  constructor(public fn, private scheduler) {}

  public get dirty() {
    return this._dirtyLevel === DirtyLevels.Dirty;
  }

  public set dirty(value) {
    this._dirtyLevel = value ? DirtyLevels.Dirty : DirtyLevels.NoDirty;
  }
  run() {
    this._dirtyLevel = DirtyLevels.NoDirty; // 运行后有缓存不脏
    ...
    }

3.2 判断computed参数

如果传递的是方法,则为get方法,set为空方法,如果传递是对象,则从中取出getset方法

export function computed(getterOrOptions) {
  let onlyGetter = isFunction(getterOrOptions);
  let getter;
  let setter;
  if (onlyGetter) {
    getter = getterOrOptions;
    setter = () => {};
  } else {
    getter = getterOrOptions.get;
    setter = getterOrOptions.set;
  }
  return new ComputedRefImpl(getter, setter);
}

3.3实现依赖收集

class ComputedRefImpl {
  public dep = undefined;
  public effect = undefined;
  __v_isRef = true; // 代表是ref,需要用.value 取值
  public _value;
  constructor(getter, public setter) {
    // 此处不能使用effect 会立即执行,ReactiveEffect可以手动调用run方法执行
    this.effect = new ReactiveEffect(
      () => getter(this._value),
      () => {
        // 此方法为getter依赖的属性变化后执行的方法:scheduler,应该触发effect渲染
        triggerRefValue(this);
      }
    );
  }

  get value() {
    if (this.effect.dirty) {
      this._value = this.effect.run();
      // 如果当前在effect中访问了计算属性,计算属性可以收集effect
      // 将当前的ref上的dep与activeEffect关联,完成收集
      trackRefValue(this);
    }
    return this._value;
  }

  set value(value: any) {
    this.setter(value);
  }
}

3.4 实现数据更新

到上面为止,依赖的属性变化,会触发更新,但是数据还是原来的数据,如果我们需要在triggerEffects中将effect._dirtyLevel = DirtyLevels.Dirty;
下次取值时判断为Dirty,再次收集,执行

4.实现(Vue3初始版本)

import { isFunction } from "@vue/shared";
import { ReactiveEffect } from "./effect";
const noop = () => {};

class ComputedRefImpl {
  dep = undefined;
  effect = undefined;
  __v_isRef = true; // 代表是ref,需要用.value 取值
  _dirty = true;
  _value;
  constructor(getter, public setter) {
    // 此处不能使用effect 会立即执行,ReactiveEffect可以手动调用run方法执行
    this.effect = new ReactiveEffect(getter, () => {
      // 此方法为getter依赖的属性变化后执行的方法:scheduler
      this._dirty = true;
    });
  }

  get value() {
    if (this._dirty) {
      this._value = this.effect.run();
      this._dirty = false;
    }
    return this._value;
  }

  set value(value: any) {
    this.setter(value);
  }
}

export function computed(getterOrOptions) {
  let onlyGetter = isFunction(getterOrOptions);
  let getter;
  let setter;
  if (onlyGetter) {
    getter = getterOrOptions;
    setter = noop;
  } else {
    getter = getterOrOptions.get;
    setter = getterOrOptions.set;
  }
  return new ComputedRefImpl(getter, setter);
}

此时已经简单实现了computed,以下情况我们修改了state.name的值,理论上应该执行effect重新渲染页面,但是并未执行

const state = reactive({
        name: "orange",
      });
      let aliasName = computed(() => {
        console.log("执行计算方法");
        return "_" + state.name;
      });

      effect(() => {
        app.innerHTML = aliasName.value;
      });

      setTimeout(() => {
        state.name = "apple";
      }, 1000);

计算属性aliasName收集了state.nameeffect收集了计算属性aliasName,同时让计算属性收集effect,当计算属性依赖的属性变化后,不仅修改dirty值,同时触发effect

 get value() {
    // 证明是在effect中使用的
    if (activeEffect) {
      trackEffects(this.dep || (this.dep = new Set()));
    }
    if (this._dirty) {
      this._value = this.effect.run();
      this._dirty = false;
    }
    return this._value;
  }
this.effect = new ReactiveEffect(getter, () => {
      // 此方法为getter依赖的属性变化后执行的方法:scheduler
      this._dirty = true;
      triggerEffects(this.dep);
    });

调整effect.ts文件,抽出来方法trackEffectstriggerEffects

const targetMap = new WeakMap();
export function track(target, key) {
  // 如果取值操作没有发生在effect中,则不需要收集依赖
  if (!activeEffect) {
    return;
  }
  // 判断是否已经记录过
  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) {
  let shouldTrack = !dep.has(activeEffect);
  if (shouldTrack) {
    dep.add(activeEffect);
    activeEffect.deps.push(dep); //同时effect记住当前这个属性
  }
}

export function trigger(target, key, newValue, oldValue) {
  // 通过对象找到对应的属性 让这个属性对应的effect重新执行

  const depsMap = targetMap.get(target);
  if (!depsMap) {
    return;
  }
  const dep = depsMap.get(key); // name 或者 age对应的所有effect

  triggerEffects(dep);
}

export function triggerEffects(dep) {
  const effects = [...dep];
  // 运行的是数组 删除的是set
  effects &&
    effects.forEach((effect) => {
      // 正在执行的effect ,不要多次执行
      if (effect !== activeEffect) {
        if (!effect.scheduler) {
          effect.run();
        } else {
          effect.scheduler();
        }
      }
    });
}
显示全文