组件的渲染流程由三个步骤组成:
- 创建 VNode
- 渲染 VNode
- 生成 DOM
其中渲染 VNode 的过程主要就是在挂载组件,而挂载组件又分为三个步骤:
-
创建组件实例(createComponentInstance)
- Vue 用一个对象 instance 来定义组件实例
- 在 instance 上初始化渲染上下文(instance.ctx)
- 初始化根组件指针(instance.root)
- 初始化派发事件方法(instance.emit)
- 初始化了各种状态属性、生命周期属性等四十多个变量
-
设置组件实例(setupComponent)
-
利用 instance 和 instance.props 初始化了组件的 props
-
利用 instance 和 instance.children 初始化了组件的插槽(slot)
-
根据 instance.shapeFlag 判断组件是否是一个有状态的组件,如果组件是有状态的组件,将进入设置组件状态的方法(setupStatefulComponent)中。
设置组件状态的方法(setupStatefulComponent)中,做了以下事情:
-
创建渲染上下文代理(instance.proxy)
- 代理的作用:Vue3 为了方便我们维护,在组件中提供了 setupState、ctx、data、props 等多种数据存储方式。instance.proxy 相当于一层代理层,对渲染上下文(instance.ctx)属性进行访问(get)和修改(set)的操作,可以经由代理层代替我们找到真实的 setupState、ctx、data、props 中的数据的访问和修改。
- proxy 的三个方法,当我们直接通过 this 访问或设置属性时,就会进入代理:
- get:当我们访问 instance.ctx 渲染上下文的属性时,就会进入 get 函数,并把我们访问的属性值作为 key 值传入函数,get方法的处理步骤如下:
- 当 key 以 $ 开头时,基本就进入 Vue 内部数据的判断处理:
- 判断是不是 Vue 内部公开的属性或方法
- 判断是不是 vue-loader 编译注入的 css 模块内部的 key
- 判断是不是用户自定义以 $ 开头的 key
- 最后判断是不是全局属性
- 当 key 不以 $ 开头:
- 会调用 hasOwn 方法,依次判断是否存在于各个数据池中:判断顺序为 setupState、data、props、ctx,当多个数据池中都存在 key 时,数据获取的优先级由这个顺序决定。比如 data 和 setupState 中都存在变量 a,那么优先取 setupState 里面的 a 的值。
- 在 get 方法中定义了 accessCache 缓存对象,在第一次获取 key 对应的数据后,我们利用 accessCache[key] 去缓存数据,当再次访问这个 key 的值时,我们就可以直接通过 accessCache[key] 获取对应的值,不需要依次调用 hasOwn 去判断了,这样可以节省下部分性能开销。
- 当 key 以 $ 开头时,基本就进入 Vue 内部数据的判断处理:
- set:set 函数主要就是对渲染上下文(instance.ctx)中的属性赋值,和 get 方法一样,执行顺序依次为 setupState、data、props。注意,如果我们直接对 props 中的数据赋值、或者对 Vue 内部以 $ 开头的保留属性赋值,都会收到一条警告。
- has:当我们判断属性是否存在于渲染上下文(instance.ctx)中时,就会进入 has 函数。has 函数会依次寻找 accessCache、setupState、data、props、用户数据、公开属性、全局属性,然后返回结果。
- get:当我们访问 instance.ctx 渲染上下文的属性时,就会进入 get 函数,并把我们访问的属性值作为 key 值传入函数,get方法的处理步骤如下:
-
判断处理 setup 函数,首先会判断我们的组件里是否定义了 setup 函数,如果定义了 setup 则进入以下流程
- 通过创建函数(createSetupContext)创建 setup 函数上下文(setupContext)对象,这个对象包含了组件的属性(attrs)、插槽(slots)和派发事件(emits)三个属性
- 通过 callWithErrorHandling 执行 setup 函数并获取结果,callWithErrorHandling 内部通过 try catch 包裹运行 setup,捕获到的报错用 handleError 处理
- 通过 handleSetupResult 函数,处理 setup 函数的运行结果(setupResult):
- 当运行结果(setupResult)是一个对象时,我们通过 reactive 把它变成响应式,并赋值给 instance.setupState,这样在模板渲染的时候,依据前面的代理规则,实例上下文(instance.ctx) 就可以从 instance.setupState 上获取到对应的数据,这就在 setup 函数与模板渲染间建立了联系
- 当运行结果(setupResult)是一个函数时,它将作为组件的渲染函数,运行渲染出组件内容
-
完成组件实例设置,无论组件是否定义了 setup ,都会执行这个函数(finishComponentSetup),函数主要做了两件事情:标准化模板或渲染函数、兼容 Options API:
- 标准化模板或渲染函数,多种不同的 Vue 项目开发模式,都在这里进行处理,这里主要是处理无法正常进行编译的情况,处理完成后,将会把组件的 render 函数赋值给 instance.render,到了组件渲染的时候,就可以运行 instance.render 函数生成组件的子树 vnode 了
- 兼容 Options 是通过 applOptions 方法实现的,在这个流程过后, Options 里的数据、函数、生命周期等也会编译进组件实例里。
-
-
-
设置并运行带副作用的渲染函数