温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

怎么理解Vue 3.0的响应式系统

发布时间:2021-11-05 17:07:16 来源:亿速云 阅读:153 作者:iii 栏目:web开发

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

一个基本的例子

Vue 3.0 的响应式系统是独立的模块,可以完全脱离 Vue 而使用,所以我们在 clone 了源码下来以后,可以直接在 packages/reactivity 模块下调试。

  1.  在项目根目录运行 yarn dev reactivity,然后进入 packages/reactivity 目录找到产出的 dist/reactivity.global.js 文件。

  2.  新建一个 index.html,写入如下代码:   

<script src="./dist/reactivity.global.js"></script>     <script>     const { reactive, effect } = VueObserver     const origin = {       count: 0     }     const state = reactive(origin)     const fn = () => {       const count = state.count       console.log(`set count to ${count}`)     }     effect(fn)     </script>

    3.  在浏览器打开该文件,于控制台执行 state.count++,便可看到输出 set count to 1。

在上述的例子中,我们使用 reactive() 函数把 origin 对象转化成了 Proxy 对象 state;使用 effect() 函数把 fn() 作为响应式回调。当 state.count 发生变化时,便触发了 fn()。接下来我们将以这个例子结合上文的流程图,来讲解这套响应式系统是怎么运行的。

初始化阶段

怎么理解Vue 3.0的响应式系统

在初始化阶段,主要做了两件事。

  1.  把 origin 对象转化成响应式的 Proxy 对象 state。

  2.  把函数 fn() 作为一个响应式的 effect 函数。

首先我们来分析第一件事。

大家都知道,Vue 3.0 使用了 Proxy 来代替之前的 Object.defineProperty(),改写了对象的 getter/setter,完成依赖收集和响应触发。但是在这一阶段中,我们暂时先不管它是如何改写对象的 getter/setter 的,这个在后续的”依赖收集阶段“会详细说明。为了简单起见,我们可以把这部分的内容浓缩成一个只有两行代码的 reactive() 函数:

export function reactive(target) {    const observed = new Proxy(target, handler)    return observed  }

完整代码在 reactive.js。这里的 handler 就是改造 getter/setter 的关键,我们放到后文讲解。

接下来我们分析第二件事。

当一个普通的函数 fn() 被 effect() 包裹之后,就会变成一个响应式的 effect 函数,而 fn() 也会被立即执行一次。

由于在 fn() 里面有引用到 Proxy 对象的属性,所以这一步会触发对象的 getter,从而启动依赖收集。

除此之外,这个 effect 函数也会被压入一个名为”activeReactiveEffectStack“(此处为 effectStack)的栈中,供后续依赖收集的时候使用。

来看看代码(完成代码请看 effect.js):

export function effect (fn) {    // 构造一个 effect    const effect = function effect(...args) {      return run(effect, fn, args)    }    // 立即执行一次    effect()    return effect  }  export function run(effect, fn, args) {    if (effectStack.indexOf(effect) === -1) {      try {        // 往池子里放入当前 effect        effectStack.push(effect)        // 立即执行一遍 fn()        // fn() 执行过程会完成依赖收集,会用到 effect        return fn(...args)      } finally {        // 完成依赖收集后从池子中扔掉这个 effect        effectStack.pop()      }    }  }

至此,初始化阶段已经完成。接下来就是整个系统最关键的一步&mdash;&mdash;依赖收集阶段。

依赖收集阶段

怎么理解Vue 3.0的响应式系统

这个阶段的触发时机,就是在 effect 被立即执行,其内部的 fn() 触发了 Proxy 对象的 getter 的时候。简单来说,只要执行到类似 state.count 的语句,就会触发 state 的 getter。

依赖收集阶段最重要的目的,就是建立一份”依赖收集表“,也就是图示的”targetMap"。targetMap 是一个 WeakMap,其 key 值是当前的 Proxy 对象 state代理前的对象origin,而 value 则是该对象所对应的 depsMap。

depsMap 是一个 Map,key 值为触发 getter 时的属性值(此处为 count),而 value 则是触发过该属性值所对应的各个 effect。

还是有点绕?那么我们再举个例子。假设有个 Proxy 对象和 effect 如下:

const state = reactive({    count: 0,    age: 18  })  const effecteffect1 = effect(() => {    console.log('effect1: ' + state.count)  })  const effecteffect2 = effect(() => {    console.log('effect2: ' + state.age)  })  const effecteffect3 = effect(() => {    console.log('effect3: ' + state.count, state.age)  })

那么这里的 targetMap 应该为这个样子:

怎么理解Vue 3.0的响应式系统

这样,{ target -> key -> dep } 的对应关系就建立起来了,依赖收集也就完成了。代码如下:

export function track (target, operationType, key) {    const effect = effectStack[effectStack.length - 1]    if (effect) {      let depsMap = targetMap.get(target)      if (depsMap === void 0) {        targetMap.set(target, (depsMap = new Map()))      }      let dep = depsMap.get(key)      if (dep === void 0) {        depsMap.set(key, (dep = new Set()))      }      if (!dep.has(effect)) {        dep.add(effect)      }    }  }

弄明白依赖收集表 targetMap 是非常重要的,因为这是整个响应式系统核心中的核心。

响应阶段

回顾上一章节的例子,我们得到了一个 { count: 0, age: 18 } 的 Proxy,并构造了三个 effect。在控制台上看看效果:

怎么理解Vue 3.0的响应式系统

效果符合预期,那么它是怎么实现的呢?首先来看看这个阶段的原理图:

怎么理解Vue 3.0的响应式系统

当修改对象的某个属性值的时候,会触发对应的 setter。

setter 里面的 trigger() 函数会从依赖收集表里找到当前属性对应的各个 dep,然后把它们推入到 effects 和 computedEffects(计算属性) 队列中,最后通过 scheduleRun() 挨个执行里面的 effect。

由于已经建立了依赖收集表,所以要找到属性所对应的 dep 也就轻而易举了,可以看看具体的代码实现:

export function trigger (target, operationType, key) {    // 取得对应的 depsMap    const depsMap = targetMap.get(target)    if (depsMap === void 0) {      return    }    // 取得对应的各个 dep    const effects = new Set()    if (key !== void 0) {      const dep = depsMap.get(key)      dep && dep.forEach(effect => {        effects.add(effect)      })    }    // 简化版 scheduleRun,挨个执行 effect    effects.forEach(effect => {      effect()    })  }

这里的代码没有处理诸如数组的 length 被修改的一些特殊情况,感兴趣的读者可以查看 vue-next 对应的源码,或者这篇文章,看看这些情况都是怎么处理的。

至此,响应式阶段完成。

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

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

vue
AI