VUE APP的初始化及更新流程梳理

73 阅读4分钟

简介

vue是一个声明式、组件化的渐进式javaScript前端框架。

通过vueApp的创建步骤梳理vue是如何工作的,通过梳理过程掌握vue在不同的使用方式中是如何工作的、掌握vue应用的渲染和更新流程等vue核心知识。

创建流程

每个Vue应用都是通过 createApp 函数创建一个新的 app应用实例,应用实例创建好以后可以通过实例的use等方法扩展应用,最后通过 app.mount(container)将应用实例挂载到容器元素。

import { createApp } from 'vue'
//创建app实例
const app = createApp({
  /* 根组件选项 */
})

//这里可以通过app.use注册插件、app.component注册全局组件

//挂载app
app.mount(container)

这就是一个完整的创建和挂载流程,是不是很简单?现在将上面代码拆开来看每一步在vue底层都做了哪些处理:

创建应用实例。createApp函数在底层执行以下步骤:

  • 调用 ensureRenderer 函数,在默认情况下(这里处理跨平台渲染器)最终调用 baseCreateRenderer 函数,这个函数最终返回一个对象 {render, createApp} 。我称这个对象叫做“渲染器”,render函数是app挂载时调用的渲染函数;creatApp是创建app实例的工厂函数,每次都会返回新的app实例对象。
  • 调用 {render, createApp} 中的 createApp函数,createApp返回app实例。
  • 重写app.mount,增加根组件template赋值逻辑。这个逻辑让我们可以像使用jQurey一样使用vue,是一种渐进式思想的体现。
  • 返回app实例

应用创建流程图如下:

1.png

渲染流程

挂载应用。挂载应用通过调用app.mount函数实现,mount函数接收挂载容器节点。mount函数内部先将根组件转换为vnode并创建appContext(上下文),然后执行render进行渲染,所以mount的核心是render函数的执行过程。render函数执行过程如下:

  • 判断vnode,vnode === null 且 container._vnode !== null时,卸载组件。当vnode !== null时,进入patch流程
  • patch函数会根据vnode的type和shapeFlag 判断节点类型,不同的类型进入不同的process。这里以Component和普通节点(HtmlElement)为例来说明。
  • Component类型调用processComponent函数,processComponent内部根据vnode状态判断挂载Component还是更新Component,在mount阶段会进入mountComponent;更新组件时调用updateComponent函数,函数内部先判断是否需要更新(如props是否变化等),如需更新则执行组件实例的update函数
    • mountComponent函数先调用createComponentInstance函数,createComponentInstance函数返回组件实例,其结构如 {subTree, render, component, emit...};
    • 然后调用setupComponent完善组件实例相关数据。初始化props、slot,将自定义指令、组件注册到上下文,执行Component的setup,确定组件的渲染函数,将Component选项添加到组件实例,注册生命周期并执行created函数。
    • 然后调用setupRenderEffect函数创建响应式。setupRenderEffect内部声明组件挂载/更新函数(componentUpdateFn),通过ReactiveEffect创建effect并将effect绑定到组件实例,effect.run会执行componentUpdateFn函数。这里还有调度函数和批量更新,当组件依赖的响应式数据发生变化时,会执行调度函数(effect.scheduler)有兴趣可以自行了解或留言交流。
      • componentUpdateFn函数根据组件实例ismount属性判断走挂载流程还是更新流程,在这两个流程中,其核心是执行组件实例的渲染函数,渲染函数返回的vnode会添加到组件实例的subTree属性,然后执行patch函数
  • 普通节点类型调用processElement,同样的processElement根据vnode状态判断挂载节点还是更新节点。挂载节点时,先根据vnode type创建节点,然后mountChildren,mountChildren中遍历子节点然后走patch流程,这是一个深度优先的过程;更新节点时,调用patchElement函数判断当前节点是否需要更新,然后调用patchChildren找出需要更新的子节点调用patch,这里有vue节点的diff算法。

下面是render函数执行的流程图,可以结合参考:

2.png

思考

通过上面的梳理,我们可以思考下面几个问题:

  • vue父子组件生命周期的执行顺序
  • 为什么vue需要diff算法
  • 如何理解vue是渐进式框架
  • 如何描述一个vue组件