Home  >  Article  >  Web Front-end  >  How to implement computed in Vue3 responsive system

How to implement computed in Vue3 responsive system

WBOY
WBOYforward
2023-05-15 13:13:061613browse

First of all, let’s briefly review:

The core of the responsive system is a WeakMap --- Map --- Set data structure.

How to implement computed in Vue3 responsive system

#The key of WeakMap is the original object, and the value is the responsive Map. In this way, when the object is destroyed, the corresponding Map will also be destroyed.

The key of Map is each attribute of the object, and the value is a set of effect functions that depend on the attributes of this object. Then use the get method of the Proxy proxy object to collect the effect functions that depend on the properties of the object into the Set corresponding to the key. It is also necessary to proxy the set method of the object and call all the effect functions of the key when modifying the object properties.

In the last article, we implemented a relatively complete responsive system based on this idea, and then continue to implement computed today.

Implement computed

First of all, we reconstruct the previous code and separate the execution of dependency collection and triggering dependency functions into track and trigger functions:

How to implement computed in Vue3 responsive system

The logic is still to add the effect to the corresponding Set and trigger the execution of the effect function in the corresponding Set, but it is much clearer when abstracted.

Then continue to implement computed.

The use of computed is roughly like this:

const value = computed(() => {
    return obj.a + obj.b;
});

Compare the effect:

effect(() => {
    console.log(obj.a);
});

The difference is just one more return value.

So we implement computed based on effect like this:

function computed(fn) {
    const value = effect(fn);
   return value
}

Of course, the current effect has no return value, so we need to add it:

How to implement computed in Vue3 responsive system

Just record the return value and return it based on the previous execution of the effect function. This transformation is still very easy.

Now computed can return the calculated value:

How to implement computed in Vue3 responsive system

But now the data is passed and all effects are executed. The effect here like computed does not need to be re-executed every time, it only needs to be executed after the data changes.

So we add a lazy option to control the effect not to be executed immediately, but to return the function and let the user execute it themselves.

How to implement computed in Vue3 responsive system

Then when using effect in computed, add a lazy option so that the effect function does not execute but returns.

Create an object in computed, and call this function to get the latest value when the get value is triggered:

How to implement computed in Vue3 responsive system

Let’s test:

How to implement computed in Vue3 responsive system

You can see that the value attribute of the computed return value can now get the calculated value, and obj.a has been modified. Afterwards, the calculation function will be re-executed, and the new value will be obtained when the value is obtained again.

Just perform one more calculation, this is because all effect functions will be executed when obj.a changes:

How to implement computed in Vue3 responsive system

In this way, every time the data changes, the computed function will be re-executed to calculate the latest value.

This is not necessary. Whether the effect function is executed or not should also be controllable. So we need to add the scheduling function to it:

How to implement computed in Vue3 responsive system

can support passing in the schduler callback function, and then when executing the effect, if there is a scheduler, pass it to it Let the user schedule it himself, otherwise the effect function will be executed.

This way the user can control the execution of the effect function:

How to implement computed in Vue3 responsive system

Then try the code again:

How to implement computed in Vue3 responsive system

#As you can see, after obj.a changed, the effect function was not executed to recalculate, because we added sheduler to schedule it ourselves. This avoids the need to execute the computed function immediately after the data changes, and you can control the execution yourself.

Now there is another problem. Each time res.value is accessed, it must be calculated:

How to implement computed in Vue3 responsive system

能不能加个缓存呢?只有数据变了才需要计算,否则直接拿之前计算的值。

当然是可以的,加个标记就行:

How to implement computed in Vue3 responsive system

scheduler 被调用的时候就说明数据变了,这时候 dirty 设置为 true,然后取 value 的时候就重新计算,之后再改为 false,下次取 value 就直接拿计算好的值了。

我们测试下:

How to implement computed in Vue3 responsive system

我们访问 computed 值的 value 属性时,第一次会重新计算,后面就直接拿计算好的值了。

修改它依赖的数据后,再次访问 value 属性会再次重新计算,然后后面再访问就又会直接拿计算好的值了。

至此,我们完成了 computed 的功能。

但现在的 computed 实现还有一个问题,比如这样一段代码:

let res = computed(() => {
    return obj.a + obj.b;
});
effect(() => {
    console.log(res.value);
});

我们在一个 effect 函数里用到了 computed 值,按理说 obj.a 变了,那 computed 的值也会变,应该触发所有的 effect 函数。

但实际上并没有:

How to implement computed in Vue3 responsive system

这是为什么呢?

这是因为返回的 computed 值并不是一个响应式的对象,需要把它变为响应式的,也就是 get 的时候 track 收集依赖,set 的时候触发依赖的执行:

How to implement computed in Vue3 responsive system

我们再试一下:

How to implement computed in Vue3 responsive system

现在 computed 值变了就能触发依赖它的 effect 了。至此,我们的 computed 就很完善了。

完整代码如下:

const data = {
    a: 1,
    b: 2
}
let activeEffect
const effectStack = [];
function effect(fn, options = {}) {
  const effectFn = () => {
      cleanup(effectFn)
      activeEffect = effectFn
      effectStack.push(effectFn);
      const res = fn()
      effectStack.pop()
      activeEffect = effectStack[effectStack.length - 1]
      return res
  }
  effectFn.deps = []
  effectFn.options = options;
  if (!options.lazy) {
    effectFn()
  }
  return effectFn
}
function computed(fn) {
    let value
    let dirty = true
    const effectFn = effect(fn, {
        lazy: true,
        scheduler(fn) {
            if(!dirty) {
                dirty = true
                trigger(obj, 'value');
            }
        }
    });
    const obj = {
        get value() {
            if (dirty) {
                value = effectFn()
                dirty = false
            }
            track(obj, 'value');
            console.log(obj);
            return value
        }
    }
    return obj
}
function cleanup(effectFn) {
    for (let i = 0; i < effectFn.deps.length; i++) {
        const deps = effectFn.deps[i]
        deps.delete(effectFn)
    }
    effectFn.deps.length = 0
}
const reactiveMap = new WeakMap()
const obj = new Proxy(data, {
    get(targetObj, key) {
        track(targetObj, key);

        return targetObj[key]
   },
   set(targetObj, key, newVal) {
        targetObj[key] = newVal

        trigger(targetObj, key)
    }
})
function track(targetObj, key) {
    let depsMap = reactiveMap.get(targetObj)
    if (!depsMap) {
    reactiveMap.set(targetObj, (depsMap = new Map()))
    }
    let deps = depsMap.get(key)
    if (!deps) {
    depsMap.set(key, (deps = new Set()))
    }
    deps.add(activeEffect)
    activeEffect.deps.push(deps);
}
function trigger(targetObj, key) {
    const depsMap = reactiveMap.get(targetObj)
    if (!depsMap) return
    const effects = depsMap.get(key)
    const effectsToRun = new Set(effects)
    effectsToRun.forEach(effectFn => {
        if(effectFn.options.scheduler) {
            effectFn.options.scheduler(effectFn)
        } else {
            effectFn()
        }
    })
}

The above is the detailed content of How to implement computed in Vue3 responsive system. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete