实现 computed 函数

未匹配的标注

特点

计算属性中的 getset 用的是类中的属性访问器,而类中的属性访问器编译后的结果就是 defineProperty
默认不会执行,多次取值的时候,只要计算属性中依赖的值不变,那么就不会重新执行
传值的时候,如果传一个对象的话,就要传 getset,如果传一个函数的话,就是 get

第 1 步

packages/reactivity/src/computed.ts

import { isFunction } from "@vue/shared";
import { ReactiveEffect } from "./effect";

class ComputedRefImpl{
  // 计算属性也有依赖收集
  public dep
  // 判断是否是一个脏的
  public _dirty = true
  public __v_isRef = true
  // 计算属性本身就是一个 effect
  public effect
  public _value
  constructor(getter, public setter) {
    // 给当前的 effect 赋值,将计算属性包成一个 effect
    this.effect = new ReactiveEffect(getter)
  }

  // 取值
  get value() {
    this._value = this.effect.run()
    return this._value
  }

  // 设置值
  set value(newValue) {
    // 如果修改计算属性的值,那么则直接触发自己写的 set 函数
    this.setter(newValue)
  }
}

export function computed(getterOrOption) {
  // 判断传过来的是否是 get + set 或者是只有一个 get
  const onlyGetter = isFunction(getterOrOption)
  // get 函数
  let getter
  // set 函数
  let setter
  if (onlyGetter) {
    // 只有 get 函数
    getter = getterOrOption
    setter = () => {}
  } else {
    // 有 get 和 set
    getter = getterOrOption.get
    setter = getterOrOption.set
  }

  return new ComputedRefImpl(getter, setter)
}

packages/reactivity/src/index.ts

export { effect } from './effect'
export { reactive } from './reactive'

// --------------------------新增 start--------------------------
import { computed } from "./computed"
// --------------------------新增 end----------------------------

packages/shared/src/index.ts

export function isObject(value: unknown): value is Record<any, any> {
  return typeof value === 'object' && value !== null
}

// --------------------------新增 start--------------------------
export function isFunction(value: unknown): boolean {
  return typeof value === 'function'
}
// --------------------------新增 end----------------------------

packages/reactivity/example/computed.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script src="../dist/reactivity.global.js"></script>
<script>
  const {reactive, effect, computed} = VueReactivity
  const obj = {
    name: 'xiaoming',
    age: 12
  }
  const state = reactive(obj)
  const age = computed({
    get() {
      console.log('get');
      return state.age + 10
    },
    set(value) {
      console.log(value);
    }
  })

  effect(() => {
    console.log(age.value);
    console.log(age.value);
    console.log(age.value);
  })

  setTimeout(() => {
    state.age = 20
  }, 2000)
</script>
</body>
</html>

效果如下:

实现 computed 函数

第 2 步

问题描述:上面当多次调用计算属性的时候,会多次执行,并没有缓存
packages/reactivity/src/computed.ts

import { isFunction } from "@vue/shared";
import { isTracking, ReactiveEffect } from "./effect";


class ComputedRefImpl{
  // 计算属性也有依赖收集
  public dep
  // 判断是否是一个脏的
  public _dirty = true
  public __v_isRef = true
  // 计算属性本身就是一个 effect
  public effect
  public _value
  constructor(getter, public setter) {
    // 给当前的 effect 赋值,将计算属性包成一个 effect
    // 这里将计算属性变成了 effect,那么计算属性中的属性会收集这个 effect

    this.effect = new ReactiveEffect(getter)
  }

  // 取值
  get value() {
        // --------------------------新增 start--------------------------
    if (this._dirty) {
      // 将结果缓存到 _value 中,这样就不用每次都调用 run 方法
      this._value = this.effect.run()
      this._dirty = false
    }
    return this._value
    // --------------------------新增 end----------------------------
  }

  // 设置值
  set value(newValue) {
    // 如果修改计算属性的值,那么则直接触发自己写的 set 函数
    this.setter(newValue)
  }
}

export function computed(getterOrOption) {
  // 判断传过来的是否是 get + set 或者是只有一个 get
  const onlyGetter = isFunction(getterOrOption)
  // get 函数
  let getter
  // set 函数
  let setter
  if (onlyGetter) {
    // 只有 get 函数
    getter = getterOrOption
    setter = () => {}
  } else {
    // 有 get 和 set
    getter = getterOrOption.get
    setter = getterOrOption.set
  }

  return new ComputedRefImpl(getter, setter)
}

效果如下:

实现 computed 函数

第 3 步

问题描述:将计算属性缓存了之后,当计算属性发生改变之后,拿到的依然是老值
packages/reactivity/src/computed.ts

import { isFunction } from "@vue/shared";
import { isTracking, ReactiveEffect, trackEffects, triggerEffects } from "./effect";


class ComputedRefImpl{
  // 计算属性也有依赖收集
  public dep
  // 判断是否是一个脏的
  public _dirty = true
  public __v_isRef = true
  // 计算属性本身就是一个 effect
  public effect
  public _value
  constructor(getter, public setter) {
    // 给当前的 effect 赋值,将计算属性包成一个 effect
    // 这里将计算属性变成了 effect,那么计算属性中的属性会收集这个 effect
        // --------------------------新增 start--------------------------
    this.effect = new ReactiveEffect(getter, () => {
      // 当计算属性依赖的值变化的时,不要重新执行计算属性的 effect,而是调用此函数
      if (!this._dirty) {
        this._dirty = true
        triggerEffects(this.dep)
      }
    })
    // --------------------------新增 end----------------------------
  }

  // 取值
  get value() {
         // --------------------------新增 start--------------------------
    // 判断是否在 effect 中取值的
    if (isTracking()) {
      trackEffects(this.dep || (this.dep = new Set))
    }
    // --------------------------新增 end----------------------------

    if (this._dirty) {
      // 将结果缓存到 _value 中,这样就不用每次都调用 run 方法
      this._value = this.effect.run()
      this._dirty = false
    }
    return this._value
  }

  // 设置值
  set value(newValue) {
    // 如果修改计算属性的值,那么则直接触发自己写的 set 函数
    this.setter(newValue)
  }
}

export function computed(getterOrOption) {
  // 判断传过来的是否是 get + set 或者是只有一个 get
  const onlyGetter = isFunction(getterOrOption)
  // get 函数
  let getter
  // set 函数
  let setter
  if (onlyGetter) {
    // 只有 get 函数
    getter = getterOrOption
    setter = () => {}
  } else {
    // 有 get 和 set
    getter = getterOrOption.get
    setter = getterOrOption.set
  }

  return new ComputedRefImpl(getter, setter)
}

packages/reactivity/src/effect.ts

...
// 需要记录 effect 依赖了哪些属性,同时让属性记录依赖了哪个effect
export class ReactiveEffect {
  // 表示是否是响应式
  active = true
  // 表示 effect 依赖了哪些属性,同时当前属性依赖了哪个 effect
  deps = []

    // --------------------------新增 start--------------------------
  constructor(public fn, public scheduler?) {

  }
  // --------------------------新增 end----------------------------

  ...
}

...

export function track(target, key) {
  // 只有写在 effect 中的属性才需要收集,所以这里判断一下
  if (!isTracking) {
    return
  }
  // 判断 weakmap 中是否已经保存过该对象(target 在 weakmap 中是 key)
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }

  // 判断 weakmap 中是否已经保存过当前属性(当前属性在 weakmap 中是 value)
  let dep = depsMap.get(key)
  if (!dep) {
    // 用 Set 类型是为了防止重复
    dep = new Set()
    depsMap.set(key, dep)
  }
  // --------------------------新增 start--------------------------
  trackEffects(dep)
    // --------------------------新增 end----------------------------
}

// --------------------------新增 start--------------------------
export function trackEffects(dep) {
  // 判断当前 key 中是否已经有了当前的 effect
  const shouldTrack = dep.has(activeEffect)
  if (!shouldTrack) {
    dep.add(activeEffect)

    // 让 effect 来记录当前属性
    activeEffect.deps.push(dep)
  }
}
// --------------------------新增 end----------------------------

export function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // 说明当前修改的对象并没有依赖任何 effect
    return
  }

  const deps = []
  if (key !== undefined) {
    deps.push(depsMap.get(key))
  }

  let effects = []
  for (const dep of deps) {
    effects.push(...dep)
  }
    // --------------------------新增 start--------------------------
  triggerEffects(effects)
  // --------------------------新增 end----------------------------
}

// --------------------------新增 start--------------------------
export function triggerEffects(dep) {
  for (const effect of dep) {
    if (effect !== activeEffect) {
      if (effect.scheduler) {
        return effect.scheduler()
      }
      effect.run()
    }
  }
}
// --------------------------新增 end----------------------------

packages/reactivity/example/computed.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script src="../dist/reactivity.global.js"></script>
<script>
  const {reactive, effect, computed} = VueReactivity
  const obj = {
    name: 'xiaoming',
    age: 12
  }
  const state = reactive(obj)
  const age = computed({
    get() {
      console.log('get');
      return state.age + 10
    },
    set(value) {
      console.log(value);
    }
  })

  effect(() => {
    console.log(age.value);
    console.log(age.value);
    console.log(age.value);
  })

  setTimeout(() => {
    state.age = 20
  }, 2000)
</script>
</body>
</html>

效果如下:
实现 computed 函数

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~