Vue3的Composition API简易实现-computed

916 阅读2分钟

前言

  • vue3的reactivity源码地址
  • reactivity 响应式系统
  • 实现的composition API有:
  • computed
  • 默认不执行, 通过lazy = true 是去判断
  • 多次取值时要实现缓存, 通过dirty = true, 判断是脏的 就去执行传递的函数, 然后dirty = false
  • 将传递的函数封装成effect, 将其将函数里的依赖的响应式变量 收集computed effect
  • computed取值时要做依赖收集track(vue2 是依赖值做的全部收集 即收集computed watcher , 也要收集渲染 watcher)
  • 当依赖值发生变化时, 发布effect时 判断当前effect是 computed effecteffect.options.scheduler
  • dirty = true, 并且trigger, computed取值时收集的effect
  • 具体以下方示例为主

示例

<script src="../node_modules/@vue/reactivity/dist/reactivity.global.js"></script>
<div id="app"></div>
<script>
    let { effect, reactive, ref, shallowRef, toRef, toRefs, computed } = VueReactivity

    const age = ref(18)

    // 此方法默认不会被执行
    const myAge = computed(() => {
        return age.value + 10
    })

    // 另一种写法
    // const myAge = computed({
    //     get(){},
    //     set(){}
    // })

    // 当访问属性的时候执行 第二次不执行 实现缓存
    // console.log(myAge.value)
    // console.log(myAge.value)

    // age.value = 100      // 更新age,myAge不会立刻重新计算  
    // myAge.value          // 再次计算最新值


    effect(() => {
        app.innerHTML = myAge.value
    })

    setTimeout(() => {
        age.value = 100
    }, 2000)
</script>
+---------------------+    +----------------------+
|                     |    |                      |
|       28            +--->|          110         +
|                     |    |                      |
+---------------------+    +----------------------+

shared

// 是不是个对象
export const isObject = (value) => typeof value == 'object' && value !== null
// 合并对象
export const extend = Object.assign
// 是不是数组
export const isArray = Array.isArray
// 是不是函数
export const isFunction = (value) => typeof value == 'function'
// 是不是数字
export const isNumber = (value) => typeof value == 'number'
// 是不是字符
export const isString = (value) => typeof value === 'string'
// 是不是正整数
export const isIntegerKey = (key) => parseInt(key) + '' === key


// 是不是自己的属性
let hasOwnpRroperty = Object.prototype.hasOwnProperty
export const hasOwn = (target, key) => hasOwnpRroperty.call(target, key)

// 是不是同一个值
export const hasChanged = (oldValue,value) => oldValue !== value

computed.ts

import { isFunction } from "@vue/shared/src"
import { effect, track, trigger } from "./effect"
import { TrackOpTypes, TriggerOrTypes } from "./operators"

class ComputedRefImpl {
    public _dirty = true // 用于缓存
    public _value;
    public effect;

    constructor(getter, public setter) {
        this.effect = effect(getter, {
            lazy: true,  // 默认不执行
            scheduler: () => {
                if (!this._dirty) {
                    this._dirty = true
                    trigger(this, TriggerOrTypes.SET, 'value')
                }
            }
        })
    }

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

        track(this, TrackOpTypes.GET, 'value')

        return this._value
    }

    set value(newValue) {
        this.setter(newValue)
    }
}

export function computed(getterOrOptions) {
    let getter;
    let setter;

    if (isFunction(getterOrOptions)) {
        getter = getterOrOptions
        setter = () => {
            console.warn('computed value must be readonly')
        }
    } else {
        getter = getterOrOptions.get
        setter = getterOrOptions.set
    }

    return new ComputedRefImpl(getter, setter)
}

/**
 * @description 寻找属性对应的effect 让其执行(这里只有数组和对象)
 * @param target    目标
 * @param type      类型 新增 或者 修改
 * @param key       key
 * @param newValue  新值
 * @param oldValue  老值
 */
export function trigger(target, type, key?, newValue?, oldValue?) {

  const depsMap = targetMap.get(target)
  if (!depsMap) return
  
  // 要发布的effect去重
  // 将所有的要执行的effect 全部存到一个新的集合中 最终一起执行
  const effects = new Set()
  const add = (effectsToAdd) => {
    if (effectsToAdd) {
      effectsToAdd.forEach(effect => effects.add(effect))
    }
  }

  // 数组 修改长度 traget.length = newValue
  // 如 [1,2,3,4,5] => [1,2,3,4,5].length = 1
  if (key === 'length' && isArray(target)) {
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key > newValue) {
        add(dep)
      }
    })
  } else {
    // 可能是对象
    // 这里肯定是修改 不能是新增
    // 如果是新增 depsMap.get(key) -> undefined
    if (key !== undefined) {
      add(depsMap.get(key))
    }

    // 如果修改数组中的某个索引
    // 如 arr=[1,2,3] -> arr[100]=1
    switch (type) {
      case TriggerOrTypes.ADD:
        if (isArray(target) && isIntegerKey(key)) {
          add(depsMap.get('length'))
        }
        break;
    }
  }

  // 发布 看这里
  effects.forEach((effect: any) => {
    if(effect.options.scheduler){
      effect.options.scheduler(effect)
    }else{
      effect()
    }
  })

}