miniVue3的简单实现-计算属性computed

258 阅读5分钟

1. 计算属性computed优点和简单用法

1.计算属性的简单使用

  1. computed参数传递一个函数,此函数会被vue内部处理成get函数,set函数会被默认设置成用户改变plusOne值控制台报警
    const count = ref(1)
    const plusOne = computed(() => count.value + 1)

    console.log(plusOne.value) // 2

    plusOne.value++ // error
  1. computed参数为一个对象,对象内包含用户自己编写的get和set函数,vue内部会默认使用用户编写的set和get
const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0

2.计算属性的优点

  1. 计算属性本身惰性的,可以对值结果进行缓存, 只有计算属性依赖的值发生改变后, 计算属性才会进行计算,获取最新的值。相比于普通函数,计算属性节省了不必要的函数执行造成的性能消耗
  2. 计算属性本身核心也是响应式系统的核心api effect副作用函数,track依赖收集函数和trriger触发依赖函数实现的(前面写过实现,这次直接使用,不对其进行概述),当计算属性执行时,计算属性内访问的属性值会收集计算属性的effect函数,当此值放生改变时,就会将计算属性的effect函数再次执行,更新数据

2. 计算属性逻辑实现

  1. 首先要规格化参数, 因为计算属性的参数可以传入对象和函数,所以要根据参数的类型获取和默认设置get和set函数
 const computed = (getterOrOptions) => {
    /*
    * 第一步标准化参数
    * getterOrOptions有两种格式
    * 1.用户传递的是函数,直接将getterOrOptions赋值给getter函数
    * 2.用户传递的是包含getter和setter函数的对象,分别取出getter和stter
    * 第二步进行computed函数创建 传递三个参数
    * 1.getter函数
    * 2.setter函数
    * 标识是否可以进行set设置的表示,根据getterOrOptions是否为函数和setter是否存在判断是否只读
    */
    let getter;
    let setter;
    if (isFunction(getterOrOptions)) {
        getter = getterOrOptions;
        setter = () => { console.error("不能对计算属性进行赋值") }
    } else {
        const { get, set } = getterOrOptions;
        getter = get || (() => { });
        setter = set || (() => { });
    }
    // , isFunction(getterOrOptions) || !getterOrOptions.set
    return new Computed(getter, setter)
}
  1. 下面实现Computed类,内部是computed计算属性的主要逻辑
    1. 首先dirty和_value值,dirty用来判断是否重新执行计算属性的effect副作用函数重新求值,只有当计算属性依赖的值发生改变时dirty才会在trriger函数中被设置为true,当使用计算函数时,就会重新计算。dirty的默认值是true,因为计算属性第一次执行需要计算计算属性的值。 _value用来存储计算属性的值,默认为空字符串
class Computed {
    constructor(getter, setter) {
        // getter函数
        this.getter = getter;
        // setter函数
        this.setter = setter;
        // 只读不可设置值
        //用于标识计算属性是否需要重新计算
        this.dirty = true;
        // 计算属性的值
        this._value = "";
    }
}
  1. 通过effect副作用函数封装用户书写的getter函数,当计算属性依赖的值发生改变时, 会执行计算属性的effect函数,effect函数内部执行用户书写的getter函数,得到计算属性最终的值
class Computed {
    constructor(getter, setter) {
        //..... 省略之前代码
        // 计算属性的值
        this._value = "";
         // 创建计算属性副作用函数
        this.effect = effect(getter, {
            // lazy标识为true时,effect函数不会被默认执行,只有当真正用到计算属性时才会执行effect函数进行求值
            lazy: true,
            // 当scheduler触发时表示依赖的数据改变,需要重新计算,dirty需要设置为true
            // 这个scheduler函数是依赖的值发生变化时执行的
            scheduler: () => {
                if (!this.dirty) {
                    this.dirty = true;
                    // 当计算属性值改变时,通知使用到计算属性值的地方,进行更新
                    trigger(this, TriggerOpTypes.SET, "value");
                }
            }
        })
    }
}
  1. 当用户获取计算属性值时,比如文章开始的简单使用中示例,获取计算属性值是用plusOne.value来获取, 因为获取和设置值使用的js的新特性取值函数(getter)和存值函数(setter),对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
   class Computed {
       get value () {
           //这里执行一些逻辑,然后用return返回值
       }

       set value (val) {
           // 这里执行一些逻辑,然后将指定的值修改
       }
   }

设置值比较简单, 只需要使用Computed实例化传入的setter函数即可 this.setter(val);

  1. 获取值的逻辑处理

    1. 获取值时首先要判断dirty的值,当dirty为true时说明计算依赖的值发生了改变,那就执行effect获取计算属性最新的值,获取完值后将dirty重置为false, 只有当计算属性依赖的值下次再改变时才会执行重新求值获取的过程, 否则用到计算属性时,一直是使用的上一次的求值

    2.计算属性求值完成后,需要track收集相应的依赖(就是用到计算属性值得地方的effect函数, 方便后续通知使用计算属性值得地方进行更新)

    class Computed {
        get value () {
            // 获取值时, 先根据dirty判断是否需要重新计算值,
            //如果需要就重新运行effect副作用函数,并将dirty设置为true 否则直接读取_value缓存的值
            if (this.dirty) {
                this._value = this.effect();
                this.dirty = false;
            }
            // 收集计算函数的依赖
            track(this, "value");
            // 返回value
            return this._value
        }

        set value (val) {
             this.setter(val);
        }
    }

5.Computed整体代码

class Computed {
    constructor(getter, setter) {
        // getter函数
        this.getter = getter;
        // setter函数
        this.setter = setter;
        // 只读不可设置值
        //用于标识计算属性是否需要重新计算
        this.dirty = true;
        // 计算属性的值
        this._value = "";
        // 创建计算属性副作用函数
        this.effect = effect(getter, {
            // lazy标识为true时,effect函数不会被默认执行,只有当真正用到计算属性时才会执行effect函数进行求职
            lazy: true,
            // 当scheduler触发时表示依赖的数据改编变,需要重新计算,dirty需要设置为true
            scheduler: () => {
                if (!this.dirty) {
                    this.dirty = true;
                    // 当计算属性值改变时,通知使用到计算属性值的地方,进行更新
                    trigger(this, TriggerOpTypes.SET, "value");
                }
            }
        })
    }
    get value () {
        // 获取值时, 先根据dirty判断是否需要重新计算值,如果需要就重新运行effect副作用函数,
        //并将dirty设置为true 否则直接读取_value缓存的值
        if (this.dirty) {
            this._value = this.effect();
            this.dirty = false;
        }
        // 收集计算函数的依赖
        track(this, "value");
        // 返回value
        return this._value
    }

    set value (val) {
        this.setter(val);
    }
}

3.github地址

代码链接 miniVue3文件夹为vue3的简单实现代码