目录
实现 computed
首页 web前端 Vue.js Vue3响应式系统怎么实现computed

Vue3响应式系统怎么实现computed

May 15, 2023 pm 01:13 PM
vue3 computed

首先,我们简单回顾一下:

响应式系统的核心就是一个 WeakMap --- Map --- Set 的数据结构。

Vue3响应式系统怎么实现computed

WeakMap 的 key 是原对象,value 是响应式的 Map。这样当对象销毁的时候,对应的 Map 也会销毁。

Map 的 key 就是对象的每个属性,value 是依赖这个对象属性的 effect 函数的集合 Set。然后用 Proxy 代理对象的 get 方法,收集依赖该对象属性的 effect 函数到对应 key 的 Set 中。还要代理对象的 set 方法,修改对象属性的时候调用所有该 key 的 effect 函数。

上篇文章我们按照这样的思路实现了一个比较完善的响应式系统,然后今天继续实现 computed。

实现 computed

首先,我们把之前的代码重构一下,把依赖收集和触发依赖函数的执行抽离成 track 和 trigger 函数:

Vue3响应式系统怎么实现computed

逻辑还是添加 effect 到对应的 Set,以及触发对应 Set 里的 effect 函数执行,但抽离出来清晰多了。

然后继续实现 computed。

computed 的使用大概是这样的:

const value = computed(() => {
    return obj.a + obj.b;
});
登录后复制

对比下 effect:

effect(() => {
    console.log(obj.a);
});
登录后复制

区别只是多了个返回值。

所以我们基于 effect 实现 computed 就是这样的:

function computed(fn) {
    const value = effect(fn);
   return value
}
登录后复制

当然,现在的 effect 是没有返回值的,要给它加一下:

Vue3响应式系统怎么实现computed

只是在之前执行 effect 函数的基础上把返回值记录下来返回,这个改造还是很容易的。

现在 computed 就能返回计算后的值了:

Vue3响应式系统怎么实现computed

但是现在数据一遍,所有的 effect 都执行了,而像 computed 这里的 effect 是没必要每次都重新执行的,只需要在数据变了之后执行。

所以我们添加一个 lazy 的 option 来控制 effect 不立刻执行,而是把函数返回让用户自己执行。

Vue3响应式系统怎么实现computed

然后 computed 里用 effect 的时候就添加一个 lazy 的 option,让 effect 函数不执行,而是返回出来。

computed 里创建一个对象,在 value 的 get 触发时调用该函数拿到最新的值:

Vue3响应式系统怎么实现computed

我们测试下:

Vue3响应式系统怎么实现computed

可以看到现在 computed 返回值的 value 属性是能拿到计算后的值的,并且修改了 obj.a. 之后会重新执行计算函数,再次拿 value 时能拿到新的值。

只是多执行了一次计算,这是因为 obj.a 变的时候会执行所有的 effect 函数:

Vue3响应式系统怎么实现computed

这样每次数据变了都会重新执行 computed 的函数来计算最新的值。

这是没有必要的,effect 的函数是否执行应该也是可以控制的。所以我们要给它加上调度的功能:

Vue3响应式系统怎么实现computed

可以支持传入 schduler 回调函数,然后执行 effect 的时候,如果有 scheduler 就传给它让用户自己来调度,否则才执行 effect 函数。

这样用户就可以自己控制 effect 函数的执行了:

Vue3响应式系统怎么实现computed

然后再试一下刚才的代码:

Vue3响应式系统怎么实现computed

可以看到,obj.a 变了之后并没有执行 effect 函数来重新计算,因为我们加了 sheduler 来自己调度。这样就避免了数据变了以后马上执行 computed 函数,可以自己控制执行。

现在还有一个问题,每次访问 res.value 都要计算:

Vue3响应式系统怎么实现computed

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

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

Vue3响应式系统怎么实现computed

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

我们测试下:

Vue3响应式系统怎么实现computed

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

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

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

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

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

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

但实际上并没有:

Vue3响应式系统怎么实现computed

这是为什么呢?

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

Vue3响应式系统怎么实现computed

我们再试一下:

Vue3响应式系统怎么实现computed

现在 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()
        }
    })
}
登录后复制

以上是Vue3响应式系统怎么实现computed的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

vue3项目中怎么使用tinymce vue3项目中怎么使用tinymce May 19, 2023 pm 08:40 PM

tinymce是一个功能齐全的富文本编辑器插件,但在vue中引入tinymce并不像别的Vue富文本插件一样那么顺利,tinymce本身并不适配Vue,还需要引入@tinymce/tinymce-vue,并且它是国外的富文本插件,没有通过中文版本,需要在其官网下载翻译包(可能需要翻墙)。1、安装相关依赖npminstalltinymce-Snpminstall@tinymce/tinymce-vue-S2、下载中文包3.引入皮肤和汉化包在项目public文件夹下新建tinymce文件夹,将下载的

vue3+vite:src使用require动态导入图片报错怎么解决 vue3+vite:src使用require动态导入图片报错怎么解决 May 21, 2023 pm 03:16 PM

vue3+vite:src使用require动态导入图片报错和解决方法vue3+vite动态的导入多张图片vue3如果使用的是typescript开发,就会出现require引入图片报错,requireisnotdefined不能像使用vue2这样imgUrl:require(’…/assets/test.png’)导入,是因为typescript不支持require所以用import导入,下面介绍如何解决:使用awaitimport

Vue3如何实现刷新页面局部内容 Vue3如何实现刷新页面局部内容 May 26, 2023 pm 05:31 PM

想要实现页面的局部刷新,我们只需要实现局部组件(dom)的重新渲染。在Vue中,想要实现这一效果最简便的方式方法就是使用v-if指令。在Vue2中我们除了使用v-if指令让局部dom的重新渲染,也可以新建一个空白组件,需要刷新局部页面时跳转至这个空白组件页面,然后在空白组件内的beforeRouteEnter守卫中又跳转回原来的页面。如下图所示,如何在Vue3.X中实现点击刷新按钮实现红框范围内的dom重新加载,并展示对应的加载状态。由于Vue3.X中scriptsetup语法中组件内守卫只有o

Vue3怎么解析markdown并实现代码高亮显示 Vue3怎么解析markdown并实现代码高亮显示 May 20, 2023 pm 04:16 PM

Vue实现博客前端,需要实现markdown的解析,如果有代码则需要实现代码的高亮。Vue的markdown解析库有很多,如markdown-it、vue-markdown-loader、marked、vue-markdown等。这些库都大同小异。这里选用的是marked,代码高亮的库选用的是highlight.js。具体实现步骤如下:一、安装依赖库在vue项目下打开命令窗口,并输入以下命令npminstallmarked-save//marked用于将markdown转换成htmlnpmins

怎么使用vue3+ts+axios+pinia实现无感刷新 怎么使用vue3+ts+axios+pinia实现无感刷新 May 25, 2023 pm 03:37 PM

vue3+ts+axios+pinia实现无感刷新1.先在项目中下载aiXos和pinianpmipinia--savenpminstallaxios--save2.封装axios请求-----下载js-cookienpmiJS-cookie-s//引入aixosimporttype{AxiosRequestConfig,AxiosResponse}from"axios";importaxiosfrom'axios';import{ElMess

Vue3中怎么实现选取头像并裁剪 Vue3中怎么实现选取头像并裁剪 May 29, 2023 am 10:22 AM

最终效果安装VueCropper组件yarnaddvue-cropper@next上面的安装值针对Vue3的,如果时Vue2或者想使用其他的方式引用,请访问它的npm官方地址:官方教程。在组件中引用使用时也很简单,只需要引入对应的组件和它的样式文件,我这里没有在全局引用,只在我的组件文件中引入import{userInfoByRequest}from'../js/api'import{VueCropper}from'vue-cropper&

Vue3复用组件怎么使用 Vue3复用组件怎么使用 May 20, 2023 pm 07:25 PM

前言无论是vue还是react,当遇到多处重复代码的时候,我们都会想着如何复用这些代码,而不是一个文件里充斥着一堆冗余代码。实际上,vue和react都可以通过抽组件的方式来达到复用,但如果遇到一些很小的代码片段,你又不想抽到另外一个文件的情况下,相比而言,react可以在相同文件里面声明对应的小组件,或者通过renderfunction来实现,如:constDemo:FC=({msg})=>{returndemomsgis{msg}}constApp:FC=()=>{return(

Vue3中如何使用defineCustomElement定义组件 Vue3中如何使用defineCustomElement定义组件 May 28, 2023 am 11:29 AM

使用Vue构建自定义元素WebComponents是一组web原生API的统称,允许开发者创建可复用的自定义元素(customelements)。自定义元素的主要好处是,它们可以在使用任何框架,甚至是在不使用框架的场景下使用。当你面向的最终用户可能使用了不同的前端技术栈,或是当你希望将最终的应用与它使用的组件实现细节解耦时,它们会是理想的选择。Vue和WebComponents是互补的技术,Vue为使用和创建自定义元素提供了出色的支持。你可以将自定义元素集成到现有的Vue应用中,或使用Vue来构

See all articles