main.js
import Vue from 'vue'
import App from './App.vue'
var app = new Vue({
el: '#app',
render: h => h(App)
})
App.vue
Hello {{name}}
mountComponent
时vm.$el = el
一、根节点组件
if (!prevVnode) {
// initial render
//调用在平台index.js中安装的_patch_方法
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
存在旧节点(vm.$el),创建新节点实例并删除旧节点
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
// 如果是首次patch 创建新节点
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
} else {
//存在老节点
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
...
} else {
// 如果老节点是一个真实dom 创建对应节点
if (isRealElement) {
// 不是服务端渲染 创建空节点替换旧节点
oldVnode = emptyNodeAt(oldVnode)
}
// replacing existing element
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
// create new node
// 为新vnode创建元素/组件实例
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// destroy old node
// 移除老节点
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
}
- 调用
createComponent
创建组件节点,不是组件节点则继续执行创建对应dom节点; - 根据vnode的数据结构创建真实的dom节点,如果vnode有children则会遍历这些子节点,递归调用
createElm
方法; -
insertedVnodeQueue
记录子节点创建顺序的队列,每创建一个dom元素就会往队列中插入当前的vnode,当patch完成,整个vnode对象全部转换成为真实的dom 树时,会依次调用这个队列中vnode hook的insert
方法; - insert方法是在
createComponent
vnode时安装的子类构造函数。
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm){
return
}
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
if (isDef(tag)) {
// 创建vnode对应的DOM元素节点vnode.elm
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
// 设置vnode的scope
setScope(vnode)
/* istanbul ignore if */
if (__WEEX__) {
……
} else {
// 调用createChildren遍历子节点创建对应的DOM节点
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
// 执行create钩子函数(directives events等的create hook)
// updateAttrs
// updateClass
// updateDomListeners 普通的原生事件
// updateDomProps
// updateStyle
// updateDirectives
invokeCreateHooks(vnode, insertedVnodeQueue)
}
// 将DOM元素插入到父元素中
insert(parentElm, vnode.elm, refElm)
}
}
- 执行创建component VNode时绑定的init钩子函数,创建子组件实例赋值给vnode.componentInstance,并调用$mount触发子组件渲染;
- 调用initComponent初始化组件,将子组件实例的根元素节点$el(子组件的dom树)赋值给子组件vnode.elm;
- 将vnode.elm插入到DOM Tree中(当前#app)
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
// 执行init hook生成componentInstance组件实例
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)
}
if (isDef(vnode.componentInstance)) {
// 调用initComponent初始化组件
initComponent(vnode, insertedVnodeQueue)
// 将vnode.elm插入到DOM Tree中
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
创建子组件实例并触发子组件渲染:
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
……
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
保存组件父子关系,并调用子组件构造函数,触发子组件_init
:
- parent:
instance/lifecycle
的activeInstance,此时是父组件实例 - vnode: 当前子组件vnode
export function createComponentInstanceForVnode (
vnode: any, // we know it's MountedComponentVNode but flow doesn't
parent: any, // activeInstance in lifecycle state
): Component {
const options: InternalComponentOptions = {
_isComponent: true,//表明是组件
_parentVnode: vnode,//当前组件vnode
parent//当前vue实例
}
// check inline-template render functions
const inlineTemplate = vnode.data.inlineTemplate
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render
options.staticRenderFns = inlineTemplate.staticRenderFns
}
//继承于vue的子组件的构造函数
return new vnode.componentOptions.Ctor(options)
}
创建vnode时createComponent
构建子组件构造函数:
- 构建子类构造函数,获取propsData、listeners
- 安装子类钩子函数
- 创建并返回vnode
export function createComponent (
Ctor: Class | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array,
tag?: string
): VNode | Array | void {
if (isUndef(Ctor)) {
return
}
//vue
const baseCtor = context.$options._base
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
data = data || {}
// resolve constructor options in case global mixins are applied after
// component constructor creation
resolveConstructorOptions(Ctor)
// transform component v-model data into props & events
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
// extract props
const propsData = extractPropsFromVNodeData(data, Ctor, tag)
const listeners = data.on
data.on = data.nativeOn
// install component management hooks onto the placeholder node
installComponentHooks(data)
// return a placeholder vnode
const name = Ctor.options.name || tag
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
return vnode
}
子组件初始化,并调用initInternalComponent
初始化子组件状态
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
vm._isVue = true
// merge options
if (options && options._isComponent) {
// 设置$option上的propsData _parentListeners
initInternalComponent(vm, options)
} else {
……
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
//子组件没有el,不执行挂载,子组件挂载在父组件创建子组件实例后,手动调用$mount
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
- vm.$options.parent 指向父组件实例;
- vm.$options._parentVnode 指向当前子组件实例的vnode;
- 从当前子组件vnode(options._parentVnode)中取出父组件传递的listeners、propsData、children、tag保存到当前子组件实例的$options中;
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
}
}
执行子组件render函数
Vue.prototype._render = function (): VNode {
const vm: Component = this
// _parentVnode 组件节点的外壳节点
const { render, _parentVnode } = vm.$options
……
vm.$vnode = _parentVnode
currentRenderingInstance = vm
vnode = render.call(vm._renderProxy, vm.$createElement)
currentRenderingInstance = null
……
// set parent
vnode.parent = _parentVnode
return vnode
}
二、App组件
- 此时vm.$el为null
- 处理子组件时,调用setActiveInstance设置当前实例activeInstance为子组件实例,处理完成时重置为父组件实例
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode
if (!prevVnode) {
//调用在平台index.js中安装的_patch_方法
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
……
}
restoreActiveInstance()
……
}
没有旧节点,直接创建新节点,此时没有传入parentElm
return function patch (oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
// 记录被插入的vnode队列,用于批触发insert
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
} else {
……
// 调用insert钩子
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}
- 先生成组件实例的elm(即组件跟实例节点),因为没有传入parentElm,elm并不执行插入父级,而是子组件全部patch完成后由父组件负责插入;
- 调用createChildren遍历子节点创建对应的DOM节点;
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm){
return
}
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
if (isDef(tag)) {
// 创建vnode对应的DOM元素节点vnode.elm
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
// 设置vnode的scope
setScope(vnode)
/* istanbul ignore if */
if (__WEEX__) {
} else {
// 调用createChildren遍历子节点创建对应的DOM节点
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
// 执行create钩子函数(directives events等的create hook)
// updateAttrs
// updateClass
// updateDomListeners 普通的原生事件
// updateDomProps
// updateStyle
// updateDirectives
invokeCreateHooks(vnode, insertedVnodeQueue)
}
// 将DOM元素插入到父元素中
insert(parentElm, vnode.elm, refElm)
}
} else if (isTrue(vnode.isComment)) {
// 创建注释节点vnode.elm,并插入到父元素中
vnode.elm = nodeOps.createComment(vnode.text)
insert(parentElm, vnode.elm, refElm)
} else {
// 创建文本节点vnode.elm,并插入到父元素中
vnode.elm = nodeOps.createTextNode(vnode.text)
insert(parentElm, vnode.elm, refElm)
}
}