赞
踩
版本:2.5.17。
我们使用vue-vli创建基于Runtime+Compiler的vue脚手架。
学习文档:https://ustbhuangyi.github.io/vue-analysis/components/patch.html
通过上一节的分析我们知道,当我们通过 createComponent
创建了组件 VNode,接下来会走到 vm._update
,执行 vm.__patch__
去把 VNode 转换成真正的 DOM 节点。这个过程我们在前一章已经分析过了,但是针对一个普通的 VNode 节点,接下来我们来看看组件的 VNode 会有哪些不一样的地方。
patch 的过程会调用 createElm
创建元素节点,回顾一下 createElm
的实现,它的定义在 src/core/vdom/patch.js
中:
- function createElm (
- vnode,
- insertedVnodeQueue,
- parentElm,
- refElm,
- nested,
- ownerArray,
- index
- ) {
- // ...
- if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
- return
- }
- // ...
- }
我们删掉多余的代码,只保留关键的逻辑,上面的代码会判断 createComponent(vnode, insertedVnodeQueue, parentElm, refElm) 的返回值,如果为 true 则直接结束。
createComponent其实调用的就是给逐渐VNode添加的init方法
那么接下来看一下 createComponent 方法的实现:
- function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
- let i = vnode.data
- if (isDef(i)) {
- const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
- if (isDef(i = i.hook) && isDef(i = i.init)) {
- i(vnode, false /* hydrating */)
- }
- // after calling the init hook, if the vnode is a child component
- // it should've created a child instance and mounted it. the child
- // component also has set the placeholder vnode's elm.
- // in that case we can just return the element and be done.
- if (isDef(vnode.componentInstance)) {
- initComponent(vnode, insertedVnodeQueue)
- insert(parentElm, vnode.elm, refElm)
- if (isTrue(isReactivated)) {
- reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
- }
- return true
- }
- }
- }

- let i = vnode.data
- if (isDef(i)) {
- // ...
- if (isDef(i = i.hook) && isDef(i = i.init)) {
- i(vnode, false /* hydrating */)
- // ...
- }
- // ..
- }
init方法定义在 src/core/vdom/create-component.js 中:
其实就是和组件的data.hook钩子合并的 componentVNodeHooks 钩子对象的init方法
- init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
- if (
- vnode.componentInstance &&
- !vnode.componentInstance._isDestroyed &&
- vnode.data.keepAlive
- ) {
- // kept-alive components, treat as a patch
- const mountedNode: any = vnode // work around flow
- componentVNodeHooks.prepatch(mountedNode, mountedNode)
- } else {
- const child = vnode.componentInstance = createComponentInstanceForVnode(
- vnode,
- activeInstance
- )
- child.$mount(hydrating ? vnode.elm : undefined, hydrating)
- }
- },

init
钩子函数执行也很简单,我们先不考虑 keepAlive
的情况,它是通过 createComponentInstanceForVnode
创建一个 Vue 的实例,然后调用 $mount
方法挂载子组件
createComponentInstanceForVnode
创建一个 vnode 的实例
在src/core/vdom/create-component.js中
- export function createComponentInstanceForVnode (
- vnode: any, // 组件VNode
- parent: any, // 当前vue实例vm
- ): Component {
- // 定义参数
- const options: InternalComponentOptions = {
- _isComponent: true,
- // 父VNode,是一个占位节点,Vue实例A调用B组件,B调用C组件,_parentVnode就是C组件占位符
- _parentVnode: vnode,
- //表示当前激活的子组件的父级实例,例如 app =new Vue,并调用子组件,parent就是app
- parent
- }
- ...
-
- // 由上一节我们知道组件会生成子构造器,vnode.componentOptions.Ctor 对应的就是子组件的构造函数
- return new vnode.componentOptions.Ctor(options)
- }

createComponentInstanceForVnode 函数构造的一个内部组件的参数,然后执行 new
vnode.componentOptions.Ctor(options)。
由上一节我们知道组件会生成子构造器,vnode.componentOptions.Ctor 对应的就是子组件的构造函数,
我们上一节分析了子构造器实际上是继承于 Vue 的一个构造器 Sub,相当于 new Sub(options)
这里有几个关键参数要注意几个点,_isComponent 为 true 表示它是一个组件,parent 表示当前激活的组件实例。
sub定义在 src/core/global-api/exten.js
- const Sub = function VueComponent (options) {
- this._init(options)cd
- }
所以子组件的实例化实际上就是在这个时机执行的,并且它会执行实例的 _init 方法
他们都是在src/core/instance/init.js中初始化的,只是有一些不同,我们主要来看不同的
代码在 src/core/instance/init.js 中:
- Vue.prototype._init = function (options?: Object) {
- const vm: Component = this
- // merge options
- if (options && options._isComponent) {
- // optimize internal component instantiation
- // since dynamic options merging is pretty slow, and none of the
- // internal component options needs special treatment.
- initInternalComponent(vm, options)
- } else {
- vm.$options = mergeOptions(
- resolveConstructorOptions(vm.constructor),
- options || {},
- vm
- )
- }
- // ...
- if (vm.$options.el) {
- vm.$mount(vm.$options.el)
- }
- }

合并 options 的过程有变化,_isComponent 为 true,所以走到了 initInternalComponent 过程,,而上一章普通的 VNode 节点则会走else。这个函数的实现也在当前页面 src/core/instance/init.js :
- export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
- const opts = vm.$options = Object.create(vm.constructor.options)
- // doing this because it's faster than dynamic enumeration.
- const parentVnode = options._parentVnode
- opts.parent = options.parent
- opts._parentVnode = parentVnode
-
- const vnodeComponentOptions = parentVnode.componentOptions
- opts.propsData = vnodeComponentOptions.propsData
- opts._parentListeners = vnodeComponentOptions.listeners
- opts._renderChildren = vnodeComponentOptions.children
- opts._componentTag = vnodeComponentOptions.tag
-
- if (options.render) {
- opts.render = options.render
- opts.staticRenderFns = options.staticRenderFns
- }
- }

这个过程我们重点记住以下几个点即可:opts.parent = options.parent
、opts._parentVnode = parentVnode
,它们是把之前我们通过 createComponentInstanceForVnode
函数传入的几个参数合并到内部的选项 $options
里了。
_init
函数最后执行的代码
- if (vm.$options.el) {
- vm.$mount(vm.$options.el)
- }
$mount
相当于执行 child.$mount(undefined, false)
,它最终会调用 mountComponent
方法,进而执行 vm._render()
方法
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。