likes
comments
collection
share

05.computed实现原理

作者站长头像
站长
· 阅读数 12

1.computed()定义

接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 get set 函数的对象来创建一个可写的 ref 对象

通过上面的定义,我们可以知道computed可以分成两种情况:

1.创建一个只读的计算属性ref:

	let person = reactive({
    firstName:'张',
    lastName:'三'
  })
	let fullName = computed(()=>{
      return person.firstName + '-' + person.lastName
  }) 

2.创建一个可写的计算属性ref:

	let fullName = computed({
				get(){
					return person.firstName + '-' + person.lastName
				},
				set(value){
					const nameArr = value.split('-')
					person.firstName = nameArr[0]
					person.lastName = nameArr[1]
				}
			})

2.computed()实现

我们首先知道,computed()方法是默认不执行的,只有在调用的时候才执行,并且在其依赖的对应属性未发生变化时,其值是被缓存住的,多次调用,依旧使用的是缓存里面的值。

 const age = ref(18)
const myAge = computed(() => { // 此方法默认不会被执行
    return age.value + 10;
})
 //当访问属性的时候执行 
console.log(myAge.value)
console.log(myAge.value)// 缓存

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

那我们理解了computed的一些特性后,就大致可以把computed组成结构分为:effect(lazy) + scheduler + 缓存的标识,这三大部分,那我们就来实现computed方法

1.定义computed函数

  • 简单表明一下,computed中可以传递的是一个对象,或者是一个函数,我们简单处理成我们想要的格式
export function computed(getOrOptions){
    let getter
    let setter
    if(isFunction(getOrOptions)){
        getter = getOrOptions
        setter = () => {
            console.warn('not set')
        }
    }else{
        getter = getOrOptions.get
        setter = getOrOptions.set
    }
   return new ComputedRefImpl(getter,setter)
}

2.ComputedRefImpl类的实现

  • 定义一个_dirty变量,
  • getter的执行,会让我们拿到当前函数的返回值
  • 包裹成为effect函数,当值再次变化时,会执行effect函数
class ComputedRefImpl {
    public _dirty = true
    public _value
    public effect
    constructor(public getter, public setter) {
        this.effect = effect(getter,{
            lazy:true,
        })
    }
    get value(){
        if(this._dirty){
            this._value = this.effect()
            this._dirty =false
        }
        return this._value
    }
    set value(newVal){
        this.setter(newVal)
    }
}

3.如何让值发生变化时,我们让dirty变为true,再次重新计算

  • 加上scheduler(调度机),我们只让dirty变为true
class ComputedRefImpl {
    public _dirty = true
    public _value
    public effect
    constructor(public getter, public setter) {
        this.effect = effect(getter,{
            lazy:true,
            scheduler:() => {
                if(!this._dirty){
                    this._dirty = true
                }
            }
        })
    }
}
  • 思考,scheduler的执行时间,当然是在effects列表执行时一起执行
effects.forEach((effect: any) => {
    if(effect.options.scheduler){
        effect.options.scheduler(effect);
    }else{
        effect();
    }
})

4.收集computed的依赖

class ComputedRefImpl {
    public _dirty = true
    public _value
    public effect
    constructor(public getter, public setter) {
        this.effect = effect(getter,{
            lazy:true,
            scheduler:() => {
                if(!this._dirty){
                    this._dirty = true
                    trigger(this,'set';'value')
                }
            }
        })
    }
    get value(){
        if(this._dirty){
            this._value = this.effect()
            this._dirty =false
        }
        track(this,'get','value')
        return this._value
    }
    set value(newVal){
        this.setter(newVal)
    }
}

5.贴上完整代码

import { isFunction } from "@vue/shared/src";
import { effect, track, trigger } from "./effect";
class ComputedRefImpl{
    public _dirty = true; // 默认取值时不要用缓存
    public _value;
    public effect;
    constructor(getter,public setter){ // ts 中默认不会挂载到this上
        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)
}