iis服务器助手广告
返回顶部
首页 > 资讯 > 精选 >如何手写Vue3响应式系统
  • 232
分享到

如何手写Vue3响应式系统

2023-07-06 11:07:22 232人浏览 独家记忆
摘要

这篇文章主要介绍“如何手写vue3响应式系统”,在日常操作中,相信很多人在如何手写Vue3响应式系统问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何手写Vue3响应式系统”的疑惑有所帮助!接下来,请跟着小编

这篇文章主要介绍“如何手写vue3响应式系统”,在日常操作中,相信很多人在如何手写Vue3响应式系统问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何手写Vue3响应式系统”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

响应式

首先,什么是响应式呢?

响应式就是被观察的数据变化的时候做一系列联动处理。就像一个社会热点事件,当它有消息更新的时候,各方媒体都会跟进做相关报道。这里社会热点事件就是被观察的目标。那在前端框架里,这个被观察的目标是什么呢?很明显,是状态。状态一般是多个,会通过对象的方式来组织。所以,我们观察状态对象的每个 key 的变化,联动做一系列处理就可以了。

我们要维护这样的数据结构:

如何手写Vue3响应式系统

状态对象的每个 key 都有关联的一系列 effect 副作用函数,也就是变化的时候联动执行的逻辑,通过 Set 来组织。

每个 key 都是这样关联了一系列 effect 函数,那多个 key 就可以放到一个 Map 里维护。

这个 Map 是在对象存在的时候它就存在,对象销毁的时候它也要跟着销毁。(因为对象都没了自然也不需要维护每个 key 关联的 effect 了)

而 WeakMap 正好就有这样的特性,WeakMap 的 key 必须是一个对象,value 可以是任意数据,key 的对象销毁的时候,value 也会销毁。

所以,响应式的 Map 会用 WeakMap 来保存,key 为原对象。

这个数据结构就是响应式的核心数据结构了。

比如这样的状态对象:

const obj = {    a: 1,    b: 2}

它的响应式数据结构就是这样的:

const depsMap = new Map();const aDeps = new Set();depsMap.set('a', aDeps);const bDeps = new Set();depsMap.set('b', bDeps);const ReactiveMap = new WeakMap()reactiveMap.set(obj, depsMap);

创建出的数据结构就是图中的那个:

如何手写Vue3响应式系统

如何手写Vue3响应式系统

然后添加 deps 依赖,比如一个函数依赖了 a,那就要添加到 a 的 deps 集合里:

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

也就是这样:

const depsMap = reactiveMap.get(obj);const aDeps = depsMap.get('a');aDeps.add(该函数);

这样维护 deps 功能上没啥问题,但是难道要让用户手动添加 deps 么?那不但会侵入业务代码,而且还容易遗漏。

所以肯定不会让用户手动维护 deps,而是要做自动的依赖收集。那怎么自动收集依赖呢?读取状态值的时候,就建立了和该状态的依赖关系,所以很容易想到可以代理状态的 get 来实现。通过 Object.defineProperty 或者 Proxy 都可以:

const data = {    a: 1,    b: 2}let activeEffectfunction effect(fn) {  activeEffect = fn  fn()}const reactiveMap = new WeakMap()const obj = new Proxy(data, {    get(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)        return targetObj[key]   }})

effect 会执行传入的回调函数 fn,当你在 fn 里读取 obj.a 的时候,就会触发 get,会拿到对象的响应式的 Map,从里面取出 a 对应的 deps 集合,往里面添加当前的 effect 函数。

这样就完成了一次依赖收集。

当你修改 obj.a 的时候,要通知所有的 deps,所以还要代理 set:

set(targetObj, key, newVal) {    targetObj[key] = newVal    const depsMap = reactiveMap.get(targetObj)    if (!depsMap) return    const effects = depsMap.get(key)    effects && effects.forEach(fn => fn())}

基本的响应式完成了,我们测试一下:

如何手写Vue3响应式系统

打印了两次,第一次是 1,第二次是 3。effect 会先执行一次传入的回调函数,触发 get 来收集依赖,这时候打印的 obj.a 是 1然后当 obj.a 赋值为 3 后,会触发 set,执行收集的依赖,这时候打印 obj.a 是 3

依赖也正确收集到了:

如何手写Vue3响应式系统

结果是对的,我们完成了基本的响应式!当然,响应式不会只有这么点代码的,我们现在的实现还不完善,还有一些问题。比如,如果代码里有分支切换,上次执行会依赖 obj.b 下次执行又不依赖了,这时候是不是就有了无效的依赖?

这样一段代码:

const obj = {    a: 1,    b: 2}effect(() => {    console.log(obj.a ? obj.b : 'nothing');});obj.a = undefined;obj.b = 3;

第一次执行 effect 函数,obj.a 是 1,这时候会走到第一个分支,又依赖了 obj.b。把 obj.a 修改为 undefined,触发 set,执行所有的依赖函数,这时候走到分支二,不再依赖 obj.b。

把 obj.b 修改为 3,按理说这时候没有依赖 b 的函数了,我们执行试一下:

如何手写Vue3响应式系统

第一次打印 2 是对的,也就是走到了第一个分支,打印 obj.b

第二次打印 nothing 也是对的,这时候走到第二个分支。但是第三次打印 nothing 就不对了,因为这时候 obj.b 已经没有依赖函数了,但是还是打印了。

打印看下 deps,会发现 obj.b 的 deps 没有清除

如何手写Vue3响应式系统

所以解决方案就是每次添加依赖前清空下上次的 deps。怎么清空某个函数关联的所有 deps 呢?记录下就好了。

我们改造下现有的 effect 函数:

let activeEffectfunction effect(fn) {  activeEffect = fn  fn()}

记录下这个 effect 函数被放到了哪些 deps 集合里。也就是:

let activeEffectfunction effect(fn) {  const effectFn = () => {      activeEffect = effectFn      fn()  }  effectFn.deps = []  effectFn()}

对之前的 fn 包一层,在函数上添加个 deps 数组来记录被添加到哪些依赖集合里。

get 收集依赖的时候,也记录一份到这里:

如何手写Vue3响应式系统

这样下次再执行这个 effect 函数的时候,就可以把这个 effect 函数从上次添加到的依赖集合里删掉:

如何手写Vue3响应式系统

cleanup 实现如下:

function cleanup(effectFn) {    for (let i = 0; i < effectFn.deps.length; i++) {        const deps = effectFn.deps[i]        deps.delete(effectFn)    }    effectFn.deps.length = 0}

effectFn.deps 数组记录了被添加到的 deps 集合,从中删掉自己。全删完之后就把上次记录的 deps 数组置空。

我们再来测试下:

如何手写Vue3响应式系统

无限循环打印了,什么鬼?

问题出现在这里:

如何手写Vue3响应式系统

set 的时候会执行所有的当前 key 的 deps 集合里的 effect 函数。

而我们执行 effect 函数之前会把它从之前的 deps 集合中清掉:

如何手写Vue3响应式系统

执行的时候又被添加到了 deps 集合。这样 delete 又 add,delete 又 add,所以就无限循环了。

解决的方式就是创建第二个 Set,只用于遍历:

如何手写Vue3响应式系统

这样就不会无限循环了。

再测试一次:

如何手写Vue3响应式系统

现在当 obj.a 赋值为 undefined 之后,再次执行 effect 函数,obj.b 的 deps 集合就被清空了,所以需改 obj.b 也不会打印啥。

看下现在的响应式数据结构:

如何手写Vue3响应式系统

确实,b 的 deps 集合被清空了。那现在的响应式实现是完善的了么?也不是,还有一个问题:

如果 effect 嵌套了,那依赖还能正确的收集么?

首先讲下为什么要支持 effect 嵌套,因为组件是可以嵌套的,而且组件里会写 effect,那也就是 effect 嵌套了,所以必须支持嵌套。

我们嵌套下试试:

effect(() => {    console.log('effect1');    effect(() => {        console.log('effect2');        obj.b;    });    obj.a;});obj.a = 3;

按理说会打印一次 effect1、一次 effect2,这是最开始的那次执行。然后 obj.a 修改为 3 后,会触发一次 effect1 的打印,执行内层 effect,又触发一次 effect2 的打印。也就是会打印 effect1、effect2、effect1、effect2。

我们测试下:

如何手写Vue3响应式系统

打印了 effect1、effet2 这是对的,但第三次打印的是 effect2,这说明 obj.a 修改后并没有执行外层函数,而是执行的内层函数。为什么呢?

看下这段代码:

如何手写Vue3响应式系统

我们执行 effect 的时候,会把它赋值给一个全局变量 activeEffect,然后后面收集依赖就用的这个。

当嵌套 effect 的时候,内层函数执行后会修改 activeEffect 这样收集到的依赖就不对了。

怎么办呢?嵌套的话加一个栈来记录 effect 不就行了?

也就是这样:

如何手写Vue3响应式系统

执行 effect 函数前把当前 effectFn 入栈,执行完以后出栈,修改 activeEffect 为栈顶的 effectFn。

这样就保证了收集到的依赖是正确的。

这种思想的应用还是很多的,需要保存和恢复上下文的时候,都是这样加一个栈。

我们再测试一下:

如何手写Vue3响应式系统

现在的打印就对了。至此,我们的响应式系统就算比较完善了。

全部代码如下:

const data = {    a: 1,    b: 2}let activeEffectconst effectStack = [];function effect(fn) {  const effectFn = () => {      cleanup(effectFn)      activeEffect = effectFn      effectStack.push(effectFn);      fn()      effectStack.pop();      activeEffect = effectStack[effectStack.length - 1];  }  effectFn.deps = []  effectFn()}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) {        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);        return targetObj[key]   },   set(targetObj, key, newVal) {        targetObj[key] = newVal        const depsMap = reactiveMap.get(targetObj)        if (!depsMap) return        const effects = depsMap.get(key)        // effects && effects.forEach(fn => fn())        const effectsToRun = new Set(effects);        effectsToRun.forEach(effectFn => effectFn());    }})

总结

响应式就是数据变化的时候做一系列联动的处理。

核心是这样一个数据结构:

如何手写Vue3响应式系统

最外层是 WeakMap,key 为对象,value 为响应式的 Map。这样当对象销毁时,Map 也会销毁。Map 里保存了每个 key 的依赖集合,用 Set 组织。

我们通过 Proxy 来完成自动的依赖收集,也就是添加 effect 到对应 key 的 deps 的集合里。 set 的时候触发所有的 effect 函数执行。

这就是基本的响应式系统。

但是还不够完善,每次执行 effect 前要从上次添加到的 deps 集合中删掉它,然后重新收集依赖。这样可以避免因为分支切换产生的无效依赖。并且执行 deps 中的 effect 前要创建一个新的 Set 来执行,避免 add、delete 循环起来。此外,为了支持嵌套 effect,需要在执行 effect 之前把它推到栈里,然后执行完出栈。解决了这几个问题之后,就是一个完善的 Vue 响应式系统了。当然,现在虽然功能是完善的,但是没有实现 computed、watch 等功能,之后再实现。

最后,再来看一下这个数据结构,理解了它就理解了 vue 响应式的核心:

如何手写Vue3响应式系统

到此,关于“如何手写Vue3响应式系统”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

--结束END--

本文标题: 如何手写Vue3响应式系统

本文链接: http://www.lsjlt.com/news/358040.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何手写Vue3响应式系统
    这篇文章主要介绍“如何手写Vue3响应式系统”,在日常操作中,相信很多人在如何手写Vue3响应式系统问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何手写Vue3响应式系统”的疑惑有所帮助!接下来,请跟着小编...
    99+
    2023-07-06
  • 怎么手写Vue3响应式系统
    这篇文章主要介绍了怎么手写Vue3响应式系统的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇怎么手写Vue3响应式系统文章都会有所收获,下面我们一起来看看吧。响应式首先,什么是响应式呢?响应式就是被观察的数据变化...
    99+
    2023-07-02
  • Vue3响应式系统如何实现computed
    这篇“Vue3响应式系统如何实现computed”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Vue3响应式系统如何实现co...
    99+
    2023-07-06
  • 手写 Vue3 响应式系统(核心就一个数据结构)
    目录前言响应式总结前言 响应式是 Vue 的特色,如果你简历里写了 Vue 项目,那基本都会问响应式实现原理。而且不只是 Vue,状态管理库 Mobx 也是基于响应式实现的。那响应式...
    99+
    2024-04-02
  • Vue3 响应式系统实现 computed
    目录前言实现 computed总结前言 上篇文章我们实现了基本的响应式系统,这篇文章继续实现 computed。 首先,我们简单回顾一下: 响应式系统的核心就是一个 WeakMap ...
    99+
    2024-04-02
  • Vue3响应式系统怎么实现computed
    首先,我们简单回顾一下:响应式系统的核心就是一个 WeakMap --- Map --- Set 的数据结构。WeakMap 的 key 是原对象,value 是响应式的 Map。这样当对象销毁的时候,对应的 Map 也会销毁。Map 的 ...
    99+
    2023-05-15
    Vue3 computed
  • 如何解析Vue3的响应式原理
    本篇文章给大家分享的是有关如何解析Vue3的响应式原理,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Vue2响应式原理回顾// 1.对象响应化:遍历每个key,定义g...
    99+
    2023-06-22
  • Vue2 响应式系统之深度响应
    目录1、场景2、方案3、场景24、总结1、场景 import { observe } from "./reactive"; import Watcher from "./watche...
    99+
    2024-04-02
  • defineProperty和Proxy如何实现的响应式系统
    这篇文章主要介绍了defineProperty和Proxy如何实现的响应式系统,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。一、极简双向绑定...
    99+
    2024-04-02
  • Vue2响应式系统介绍
    目录一、响应式系统要干什么二、响应式数据三、保存当前正在执行的函数四、响应式数据五、Observer 对象六、测试七、总结前言: 目前工作中大概有 的需求是在用 ...
    99+
    2024-04-02
  • 浅谈Java响应式系统
    目录初识响应式系统什么是响应式系统响应式系统的四大特点及时响应性(Responsive)恢复性(Resilient)有弹性(Elastic)消息驱动(Message Driven)总...
    99+
    2024-04-02
  • Vue3响应式对象是如何实现的(1)
    目录简单的响应式实现Proxy与响应式为什么需要Proxy?Proxy创建的代理对象与原始对象有何不同?多副作用函数的响应式实现简单的响应式实现 为了方便说明,先来看一个简单的例子。...
    99+
    2024-04-02
  • setup+ref+reactive如何实现vue3响应式功能
    这篇文章给大家介绍setup+ref+reactive如何实现vue3响应式功能,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。setup 是用来写组合式 api ,内部的数据和方法需要通过 return 之后,模板才能...
    99+
    2023-06-21
  • Vue3响应式对象是如何实现的(2)
    目录前言分支切换的优化副作用函数嵌套产生的BUG自增/自减操作产生的BUG前言 在Vue3响应式对象是如何实现的(1)中,我们已经从功能上实现了一个响应式对象。如果仅仅满足于功能实现...
    99+
    2024-04-02
  • 如何使用 PHP 数组编写高效的响应系统?
    PHP 数组是一种非常常用的数据类型,可以用来存储一组相关的数据。在编写高效的响应系统时,使用 PHP 数组可以让我们更加方便地处理数据,提高程序的效率。本文将介绍如何使用 PHP 数组编写高效的响应系统,并提供一些示例代码。 一、使用 P...
    99+
    2023-09-01
    数组 学习笔记 响应
  • Vue2响应式系统之嵌套
    目录1、场景2、执行过程3、修复4、测试5、总结1、场景 在 开发中肯定存在组件嵌套组件的情况,类似于下边的样子。Vue <!-- parent-component ...
    99+
    2024-04-02
  • Vue2 响应式系统之数组
    目录1、场景2、场景 23、方案3、收集依赖代码实现4、通知依赖代码实现5、测试6、总结本文接Vue2响应式系统 、Vue2 响应式系统之分支切换  ,...
    99+
    2024-04-02
  • 什么是Vue响应式系统
    什么是Vue响应式系统,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。响应式系统(Reactivity systems)是现代前端框架的关键部分...
    99+
    2024-04-02
  • vue3响应式原理和api编写的方法是什么
    这篇文章主要讲解了“vue3响应式原理和api编写的方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“vue3响应式原理和api编写的方法是什么”吧!前言vue3响应式原理加api编写...
    99+
    2023-06-22
  • 如何在Bash中编写响应式程序?
    Bash是一种流行的Shell脚本语言,广泛应用于Linux和Unix系统中。虽然Bash主要用于编写脚本,但是也可以用它来编写响应式程序。在本文中,我们将介绍如何在Bash中编写响应式程序,并提供一些示例代码。 什么是响应式编程? 响...
    99+
    2023-08-08
    编程算法 bash 响应
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作