从零实现一个vue3(四) 实现computed

236 阅读2分钟

希望实现功能如下

<div id="app"></div>
<script>
  const { reactive, computed, effect } = Vue

  const obj = reactive({
    name: '张三'
  })

  const computedObj = computed(() => {
    return '姓名:' + obj.name
  })

  effect(() => {
    document.querySelector('#app').innerHTML = computedObj.value
  })

  setTimeout(() => {
    obj.name = '李四'
  }, 2000)
</script>

就是 可以通过computed 对 reactive数据 进行二次计算

看到 computedObj.value 这种写法 就知道 computed 肯定也使用了 class get set 相关方法

在实现 一个 computedObj 数据的时候 动作是什么

 const computedObj = computed(() => {
    return '姓名:' + obj.name
  })

创建packages/reactivity/src/computed.ts

import { isFunction } from '@vue/shared'
import { Dep } from './dep'
import { ReactiveEffect } from './effect'
import { trackRefValue, triggerRefValue } from './ref';

/**
 *计算属性类
 * */

export class ComputedRefImpl<T> {
  public dep?: Dep = undefined
  private _value!: T

  public readonly effect: ReactiveEffect<T>

  public readonly __v_isRef = true
  /**
   * 脏数据
   * 脏:为 false 时,表示需要触发依赖。为 true 时表示需要重新执行 run 方法,获取数据。即:数据脏了
   * */ 
  public _dirty = true
  constructor(getter) {
    // 生成副作用
    this.effect = new ReactiveEffect(getter, () => {
      // 判断当前脏的状态,如果为 false,表示需要《触发依赖》
      if (!this._dirty) {
        // 将脏置为 true,表示
        this._dirty = true
        triggerRefValue(this)
      }
    })
    this.effect.computed = this
  }

  get value() {
    // 触发依赖
    trackRefValue(this)
    // 判断当前脏的状态,如果为 true ,则表示需要重新执行 run,获取最新数据
    if (this._dirty) {
      this._dirty = false
      // 执行 run 函数
      this._value = this.effect.run()!
    }

    // 返回计算之后的真实值
    return this._value
  }

}

/**
 * 计算属性
 * */
export function computed(getterOrOptions) {
  let getter
  // 判断传入的参数是否为一个函数
  const onlyGetter = isFunction(getterOrOptions)

  if (onlyGetter) {
    // 如果是函数,则赋值给getter
    getter = getterOrOptions
  }

  const cRef = new ComputedRefImpl(getter)

  return cRef as any
}

packages/reactivity/src/effect.ts

作用是 新增scheduler, 如果有scheduler, 就直接触发scheduler 协调器相关

/**
 * 触发依赖的方法
 * @param target WeakMap 的 key
 * @param key 代理对象的 key,当依赖被触发时,需要根据该 key 获取
 * @param newValue 指定 key 的最新值
 * @param oldValue 指定 key 的旧值
 */
export function trigger(target: object, key?: unknown, newValue?: unknown) {
  console.log('trigger: 触发依赖')
  // 依据 target 获取存储的 map 实例
  const depsMap = targetMap.get(target)
  // 如果 map 不存在,则直接 return
  if (!depsMap) {
    return
  }
  let dep: Dep | undefined = depsMap.get(key)
  // dep 不存在则直接 return
  if (!dep) {
    return
  }
  // 触发 dep
  triggerEffects(dep)


}

/**
 * 依次触发 dep 中保存的依赖
 */
export function triggerEffects(dep: Dep) {
  // 把 dep 构建为一个数组
  const effects = Array.isArray(dep) ? dep : [...dep]
  // 依次触发
  // for (const effect of effects) {
  //   triggerEffect(effect)
  // }
  
  for (const effect of effects) {
    if (effect.computed) {
      triggerEffect(effect)
    }
  }
  for (const effect of effects) {
    if (!effect.computed) {
      triggerEffect(effect)
    }
  }

}

/**
 * 触发指定的依赖
 */
export function triggerEffect(effect: ReactiveEffect) {
  // 存在调度器就执行调度函数
  if (effect.scheduler) {
    effect.scheduler()
  }
  // 否则直接执行 run 函数即可
  else {
    effect.run()
  }
}

/**
 * effect 函数
 * @param fn 执行方法
 * @returns 以 ReactiveEffect 实例为 this 的执行函数
 */
export function effect<T = any>(fn: () => T, options?: ReactiveEffectOptions) {
  // 生成 ReactiveEffect 实例
  const _effect = new ReactiveEffect(fn)
  // 存在 options,则合并配置对象
  if (options) {
    extend(_effect, options)
  }
  
  if (!options || !options.lazy) {
    // 执行 run 函数
    _effect.run()
  }
}

/**
 * 响应性触发依赖时的执行类
 */
export class ReactiveEffect<T = any> {
  computed?: ComputedRefImpl<T>
  constructor(
    public fn: () => T,
    public scheduler: EffectScheduler | null = null
  ) {}

  run() {
    // 为 activeEffect 赋值
    activeEffect = this

    // 执行 fn 函数
    return this.fn()
  }
  stop() {
    
  }
}

代码地址 github.com/beewolf233/…

参考文章

vue3 源码学习,实现一个 mini-vue(四):computed 计算属性