SoFunction
Updated on 2025-04-11

Computed function design

1. Description of the functional computed

computedAs a computed attribute, its function is to describe the complex logical calculation of responsive data. When the responsive data dependent on changes, the computed attribute will be recalculated and the result of the logical calculation is updated.

There is a feature that reflects the characteristics of computing attributes, which is to compare the differences between computation attributes and methods. For example, we need to calculate the sum of two responsive data.

const obj = new reactive({foo: 1, bar: 2})
// Use the computed attribute to get the valueconst value = computed(() => { + })
// Use method to get the valueconst value = () =>  + 

The difference is that computed has a cache mechanism. When the internally dependent responsive data has not changed, the results are directly obtained from the cache, while the method needs to be executed every time. When there is a lot of responsive data dependent and the logic is very complex, the computational attribute efficiency will be much higher than that of the method.

2. lazy design of computing attributes

For calculation properties we only need to calculate when we need values,computed(getter)Take onegetterAs a parameter, it needs to be re-execute every time the responsive data dependent on the calculation property is changed.getter, but every timegetterEach execution of the calculation attribute is unnecessary because it is run only when the value of the calculated attribute is obtainedgetterGet the value, so forgetterThe calculations need to be performed lazyly.

The role of the scheduler has been introduced in the previous chapter, so we only need to add one to the schedulerlazyJust mark

effect(() => {
    return  + 
}, {
	// Set lazy mark in the scheduler    lazy: true
})
function effect(fn, options = {}) {
    const effectFn = () => {
      	// Omit the code    }
     = options
     = []
	// If there is a cache tag, it will be returned directly    if()
        return effectFn
    effectFn()
}
function computed(getter) {
    // Use getter as a side effect function    const effectFn = effect(getter, {
        lazy: true,
    })
    const obj = {
        // EffectFn will only be triggered when value is read        get value() {
            return effectFn()
        }
    }
    return 
}

This way we are only getting the computed properties.valueThe side effect function execution will be triggered only when the responsive data is changed, and it will not be executed directly.

3. Calculate attribute cache

Suppose we have the following code based on the above code design

const sum = computed(() =>  + )
()
()
()

at this timeeffectFnIt will be executed three times in a row, but the result is the same each time because the calculation attribute depends on \The value of , has not changed , so we can directly cache the calculation results.

function computed(getter) {
    // Used to cache the calculation results    let value
    // Used to mark whether the value needs to be recalculated    let dirty = true
    let effectFn = effect(getter, {
        lazy: true
    }) 
    const obj = {
        get value() {
            // If you need to recalculate the value            if(dirty) {
                value = effectFn()
                dirty = false
            }
            return value
        }
    }
    return obj
}

We obtain it many timesThe value of the value will not be recalculated every time, but there is a very obvious problem in writing this way that the value of the responsive formula we rely on will not be recalculated when it changes, so wedirtyThis tag needs to be associated with the dependent responsive data. The specific approach is todirtyPut it in the scheduler so that the scheduler changes every time the responsive data is changeddirtyValue of

function computed(getter) {
    // Used to cache the calculation results    let value
    // Used to mark whether the value needs to be recalculated    let dirty = true
    let effectFn = effect(getter, {
        lazy: true,
        // When responsive data is modified, the value of dirty is modified when the side effect function is triggered        scheduler: () => {
            dirty = true
        }
    }) 
    const obj = {
        get value() {
            // If you need to recalculate the value            if(dirty) {
                value = effectFn()
                dirty = false
            }
            return value
        }
    }
    return obj
}

In fact, there is another problem that when the calculation attribute is puteffectWhen nesting is formed, we changeobjThe value of the outer layer will not triggereffectFunction, i.e.:

// Omit the code...const sum = computed(() =>  + )
effect(() => {
    ()
})

When changing/The value ofvalue. This does not conform to the application scenario. In the application, the page will be re-rendered when our computing properties change.

The whole problem is analyzedeffectNested problems when inner layer responsive data onlygetterThe connection has nothing to do with the outer side effect function, but the innervalueIt is not responsive data or lazy execution, so it will not be related to the outer side effect function. The solution here is to directly and manually associate the results of the calculation properties with the outer side effect function.

function computed(getter) {
    let value
    let dirty = true
    const effectFn = effect(getter, {
        lazy: true,
        scheduler: () => {
            dirty = true
            // When the responsive data dependent on the calculation attribute changes, the response will be triggered manually            trigger(obj, 'value')
        }
    })
    const obj = {
        get value() {
            if(dirty) {
                value = effectFn()
                dirty = false
            }
            // Manual tracking when reading value            track(obj, 'value')
            return value
        }
    }
    return obj
}

This is the end of this article about the design of computed. For more related computed content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!