从原理上理解 vue3 的队列模型

简介: 从原理上理解 vue3 的队列模型

普通的队列模型

1686381660437.png


普通的队列模型,现实生活中随处可见,饭堂的排队,先来的先打饭。

它有以下特点:

  1. 是一个有先后顺序的列表
  2. 可以出队和入队
  3. 先进先出

vue 的队列模型


三个队列


vue 有 3 个队列,分别为组件数据更新前组件数据更新组件数据更新后队列。

3 个队列,都围绕着组件数据更新前中后执行的。

1686381642368.png

什么是组件数据更新?


<template>
  <p>{{name}}</p>
</template>
<script lang="ts">
import { ref, defineComponent } from 'vue'
export default defineComponent({
  setup(){
    const name = ref("vue setup")
    return {
      name
    }
  }
})
</script>

上面代码中,组件数据更新,就是指组件的 name 的值更新,DOM 的值也被更新的过程(虽然 DOM 值更新但 UI 仍未更新,因为 js 线程仍在运行代码)

两种不同的队列模型


vue 的 3 个队列中,有 2 种不同的队列模型

  1. 组件数据更新前(后面称 Pre 队列)、组件数据更新后队列 (后面称 Post 队列)—— 先进先出,无优先级,不可插队,允许递归
  2. 组件数据更新队列 —— 优先级高的 Job 先执行,允许插队,允许递归

这里先介绍一下组件数据更新队列


组件数据更新队列


1686381613547.png

如图所示,每个 Job 有一个属性 id,id 小的先执行,且中途可以插队。

那为什么要这么设计呢?我们拆分成几个问题一一回答:

Job 的 id 是怎么取值的?允许插队,id 小的 Job 先执行,有什么意义?

Job 的 id 是 vue 组件内部实例的 uid 属性。是一个不会重复的计数器。第一个组件 uid 是 0,第二个组件 uid 是 1,如此类推。

id 小的 Job 先执行,这保证了,父组件永远比子组件先更新(因为先创建父组件,再创建子组件,子组件可能依赖父组件的数据)

仅仅当父组件数据更新完毕,才能更新子组件


试想,如果子组件数据更新先执行,在这之后,如果父组件更新了数据(子组件依赖该数据),那么子组件还需要再执行一次数据更新。

因此,id 小的先执行,即保证了数据的正确性,又提升了数据更新的性能

这也是自上而下的**单向数据流**所决定的组件数据更新顺序。

什么是递归?什么时候允许递归?

如上图所示:当一个 Job 在执行时,将它自身再加入队列,这种情况称为递归。

默认情况下,Job 是不能递归的


允许 Job 递归的情况:组件更新的 Job、watch 的 callback(这也是个 Job)

什么情况下,组件更新会发生递归?

下面是一个例子:

定义全局属性:


const app = createApp(App)
app.config.globalProperties.$loading = {
  isLoading: ref(false),
}

父组件:


<template>
  <div>
    <div>{{ $loading.isLoading.value }}</div>
    <button @click='add'>{{ count }}</button>
    <Children :count='count' />
  </div>
</template>
<script setup lang='ts'>
// 省略 import
const count = ref(0)
function add() {
  count.value = count.value + 1
}
</script>

子组件:


<template>
  <div>
    <p>{{ count }}</p>
  </div>
</template>
<script lang='ts'>
import { ref, defineComponent, onUpdated } from 'vue'
export default defineComponent({
  props: {
    count: {
      type: Number,
      required: true
    }
  },
  watch: {
    count() {
      this.$loading.isLoading.value = !this.$loading.isLoading.value
    }
  }
})
</script>

有父子两个组件,点击 button 后,其数据流如下:

  1. 父组件 count 自增
  2. 子组件属性被修改,触发 watch,修改 loading
  3. loading 被修改,父组件更新 DOM

我们这里会发现,这个例子并不满足单向数据流:父组件正在进行数据更新时,子组件修改了全局属性,导致父组件需要进行更新。

因此父组件需要再次进入数据更新队列,再次执行更新,才能保证数据正确。这种情况就是递归。

如果过多的打破单向数据流,会导致多次递归执行更新,可能会导致性能下降


Pre/Post 队列


实际上,Pre/Post 队列,比普通队列复杂一点

1686381556123.png

该类型的队列,执行内容有哪些?

举几个例子:

  1. watch 函数有一个参数 flush,默认为 pre,会将 watch 的 callback 加入到 Pre 队列; flush 设置为 post,则会加入到 POST 队列
  2. mounted 声明周期,是在 Post 队列执行的,因为要等组件更新、 DOM 挂载上去再执行

为什么会有执行和等待两个队列?

因为执行队列时,会将 Job 加入队列,可能会加入多次,在等待队列转成执行队列过程中,可以最后统一执行一次去重

什么时候会递归?


<template>
  <div @click='add'>
    {{list}}
  </div>
</template>
<script setup>
import { reactive,watch } from 'vue'
const list = reactive([])
watch(list, ()=>{
  if(list.length < 10){
    list.push(1)
  }
})
function add(){
  list.push(1)
}
</script>

watch callback 默认在 Pre 队列执行;watch callback 里面又改变了自身,使 callback 又加入 Pre 的等待队列。

当然,一般是不会这么写直接导致递归的,递归往往是间接导致的。如:watch 修改了一个 ref,ref 触发依赖,又导致了 watch 监听的值改变,导致递归。


总结


使用队列模型,能更好的描述、更好的进行解耦, vue 的生命周期、组件更新、单向数据流等设计。

组件更新队列,使用了一个带有 Job id 的可插队队列。

  1. 延迟执行,能够对重复 Job 进行去重,提升性能
  2. 保证了,组件一定是先执行父组件,在执行子组件的数据更新,该顺序是单向数据流决定的
  3. 递归情况下,能够正确处理组件更新顺序,以及保证了数据的正确性

使用 Pre/Post 队列,:

  1. 延迟执行,能够对重复 Job 进行去重,提升性能
  2. 解析组件时,声明周期并没有立即执行,需要在特定时间执行(如组件 mounted 后)

下一篇文章,将会对 vue 队列的源码进行解析。

目录
相关文章
|
9天前
|
存储 JavaScript 前端开发
vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
【10月更文挑战第21天】 vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
|
6天前
|
JavaScript 前端开发 开发者
Vue 3中的Proxy
【10月更文挑战第23天】Vue 3中的`Proxy`为响应式系统带来了更强大、更灵活的功能,解决了Vue 2中响应式系统的一些局限性,同时在性能方面也有一定的提升,为开发者提供了更好的开发体验和性能保障。
19 7
|
10天前
|
缓存 JavaScript 搜索推荐
Vue SSR(服务端渲染)预渲染的工作原理
【10月更文挑战第23天】Vue SSR 预渲染通过一系列复杂的步骤和机制,实现了在服务器端生成静态 HTML 页面的目标。它为提升 Vue 应用的性能、SEO 效果以及用户体验提供了有力的支持。随着技术的不断发展,Vue SSR 预渲染技术也将不断完善和创新,以适应不断变化的互联网环境和用户需求。
29 9
|
7天前
|
前端开发 数据库
芋道框架审批流如何实现(Cloud+Vue3)
芋道框架审批流如何实现(Cloud+Vue3)
25 3
|
6天前
|
JavaScript 数据管理 Java
在 Vue 3 中使用 Proxy 实现数据双向绑定的性能如何?
【10月更文挑战第23天】Vue 3中使用Proxy实现数据双向绑定在多个方面都带来了性能的提升,从更高效的响应式追踪、更好的初始化性能、对数组操作的优化到更优的内存管理等,使得Vue 3在处理复杂的应用场景和大量数据时能够更加高效和稳定地运行。
24 1
|
6天前
|
JavaScript 开发者
在 Vue 3 中使用 Proxy 实现数据的双向绑定
【10月更文挑战第23天】Vue 3利用 `Proxy` 实现了数据的双向绑定,无论是使用内置的指令如 `v-model`,还是通过自定义事件或自定义指令,都能够方便地实现数据与视图之间的双向交互,满足不同场景下的开发需求。
25 1
|
9天前
|
前端开发 JavaScript
简记 Vue3(一)—— setup、ref、reactive、toRefs、toRef
简记 Vue3(一)—— setup、ref、reactive、toRefs、toRef
|
9天前
Vue3 项目的 setup 函数
【10月更文挑战第23天】setup` 函数是 Vue3 中非常重要的一个概念,掌握它的使用方法对于开发高效、灵活的 Vue3 组件至关重要。通过不断的实践和探索,你将能够更好地利用 `setup` 函数来构建优秀的 Vue3 项目。
|
12天前
|
JavaScript Java API
vue3知识点:setup
vue3知识点:setup
26 5
|
12天前
|
资源调度 JavaScript 前端开发
vue3第一章基础:创建Vue3.0工程,包括使用vue-cli 创建、使用 vite 创建
vue3第一章基础:创建Vue3.0工程,包括使用vue-cli 创建、使用 vite 创建
21 5