Vue3源码学习之路-实现computed

Vue3的computed是基于effect实现的。

computed实现思路

  • 用户使用计算属性时写法与defineProperty相似
  • 计算属性具有缓存的功能,如果值不变,多次取值只会执行一次get
  • 可以用一个标识符来实现缓存,如果值有变化就重新执行get,没有变化就不需要重新执行get
  • 所以计算属性可以是一个effect,当依赖的属性变化了就更新标识

effect方法在前面的学习中已经实现,所以在本次代码书写中可以对effect方法进行优化,公共逻辑提取,即可在computed中直接使用。

例子

packages/reactivity/dist/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="./reactivity.global.js"></script>
    <!-- <script src="../../../node_modules/@vue/reactivity/dist/reactivity.global.js"></script> -->
    <script>
      const app = document.getElementById('app');
      const { effect, reactive, computed } = VueReactivity;

      const state = reactive({ name: 'bowen', age: 18 });

      const text = computed(() => {
        return `名字:${state.name},年龄:${state.age}`;
      });

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

      setTimeout(() => {
        state.age = 24;
      }, 1000);
    </script>
  </body>
</html>

新增的shared方法

packages/shared/src/index.ts

export const isObject = (val: unknown): val is Record<any, any> =>
  val !== null && typeof val === 'object';
export const isFunction = (val: unknown): val is Function => typeof val === 'function';
export const isArray = Array.isArray;
export const assign = Object.assign;
export const isString = (val: unknown): val is string => typeof val === 'string';

effect的优化

将之前书写的track函数中effect收集逻辑提取出来命名为trackEffects

export function trackEffects(dep) {
  if (activeEffect) {
    let shouldTrack = !dep.has(activeEffect);
    if (shouldTrack) {
      dep.add(activeEffect);
      // effect记录属性,清理时用
      activeEffect.deps.push(dep);
    }
  }
}

export function track(target, type, key) {
  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);
}

将之前书写的trigger函数中的调度器以及渲染函数的执行逻辑提取出来命名为triggerEffects

export function triggerEffects(effects) {
  effects = [...effects];
  effects.forEach((effect) => {
    // 屏蔽掉相同的调用
    if (effect !== activeEffect) {
      if (effect.scheduler) {
        // 如果传入了调度器,执行调度器
        effect.scheduler();
      } else {
        effect.run();
      }
    }
  });
}

export function trigger(target, type, key, value, oldValue) {
  const depsMap = targetMap.get(target);
  if (!depsMap) {
    // 触发的值不在模板中使用
    return;
  }

  // 获取属性对应的key
  let effects = depsMap.get(key);
  if (effects) {
    triggerEffects(effects);
  }
}

ComputedRefImpl类

packages/reactivity/src/computed.ts

export class ComputedRefImpl {
  public effect;
  // 脏值,默认是脏的
  public _dirty = true;
  public readonly __v_isRef = true;
  public readonly __v_isReadonly: boolean = false;
  private _value;
  public dep = new Set();

  constructor(public getter, private readonly _setter) {
    // getter放入effect中后,将会收集依赖属性
    this.effect = new ReactiveEffect(getter, () => {
      // 当依赖属性变化的时候,会执行这个调度函数
      if (!this._dirty) {
        this._dirty = true;

        // 触发更新
        triggerEffects(this.dep);
      }
    });
  }

  // 相当于Object.defineProperty属性访问器
  get value() {
    // 依赖收集
    trackEffects(this.dep);

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

  set value(newValue) {
    this._setter(newValue);
  }
}
  • ComputedRefImpl在使用中会传入gettersetter
  • 会生成一个effect,进行依赖的属性收集工作,并且将getter放入effect中,生成effect时会传入一个调度函数,当依赖属性变化的时候,会执行这个调度函数出发更新
  • ComputedRefImpl有get和set,相当于Object.defineProperty属性访问器
  • get时进行依赖收集
  • set时直接执行传入的setter
  • _dirty用来标记value是否变化

computed函数

packages/reactivity/src/computed.ts

export const computed = (getterOrOptions) => {
  let getter = undefined;
  let setter = undefined;

  // 是否只传入了一个函数
  const onlyGetter = isFunction(getterOrOptions);

  // 如果传了一个参数,那么为getter赋值,并且没有setter
  if (onlyGetter) {
    getter = getterOrOptions;
    setter = () => {
      console.warn('Write operation failed: computed value is readonly');
    };
  } else {
    getter = getterOrOptions.get;
    setter = getterOrOptions.set;
  }

  const cRef = new ComputedRefImpl(getter, setter);

  return cRef;
};

使用computed时参数可以是一个函数或者一个包含get和set的对象,当是一个函数时即为getter,并且没有setter

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值