main.js
import Vue from 'vue'
import App from './App.vue'
var app = new Vue({
el: '#app',
render: h => h(App)
})
App.vue
<div id="NLRX"><p>Hello {{name}}</p></div>
mountComponent
時vm.$el = el
一、根節(jié)點組件
if (!prevVnode) {
// initial render
//調(diào)用在平臺index.js中安裝的_patch_方法
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
存在舊節(jié)點(vm.$el),創(chuàng)建新節(jié)點實例并刪除舊節(jié)點
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
// 如果是首次patch 創(chuàng)建新節(jié)點
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
} else {
//存在老節(jié)點
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
...
} else {
// 如果老節(jié)點是一個真實dom 創(chuàng)建對應(yīng)節(jié)點
if (isRealElement) {
// 不是服務(wù)端渲染 創(chuàng)建空節(jié)點替換舊節(jié)點
oldVnode = emptyNodeAt(oldVnode)
}
// replacing existing element
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
// create new node
// 為新vnode創(chuàng)建元素/組件實例
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
// 移除老節(jié)點
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
}
- 調(diào)用
createComponent
創(chuàng)建組件節(jié)點灼狰,不是組件節(jié)點則繼續(xù)執(zhí)行創(chuàng)建對應(yīng)dom節(jié)點筹燕; - 根據(jù)vnode的數(shù)據(jù)結(jié)構(gòu)創(chuàng)建真實的dom節(jié)點槽唾,如果vnode有children則會遍歷這些子節(jié)點旭等,遞歸調(diào)用
createElm
方法俄删; -
insertedVnodeQueue
記錄子節(jié)點創(chuàng)建順序的隊列继谚,每創(chuàng)建一個dom元素就會往隊列中插入當前的vnode,當patch完成爱榕,整個vnode對象全部轉(zhuǎn)換成為真實的dom 樹時瓣喊,會依次調(diào)用這個隊列中vnode hook的insert
方法; - insert方法是在
createComponent
vnode時安裝的子類構(gòu)造函數(shù)黔酥。
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm){
return
}
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
if (isDef(tag)) {
// 創(chuàng)建vnode對應(yīng)的DOM元素節(jié)點vnode.elm
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
// 設(shè)置vnode的scope
setScope(vnode)
/* istanbul ignore if */
if (__WEEX__) {
……
} else {
// 調(diào)用createChildren遍歷子節(jié)點創(chuàng)建對應(yīng)的DOM節(jié)點
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
// 執(zhí)行create鉤子函數(shù)(directives events等的create hook)
// updateAttrs
// updateClass
// updateDomListeners 普通的原生事件
// updateDomProps
// updateStyle
// updateDirectives
invokeCreateHooks(vnode, insertedVnodeQueue)
}
// 將DOM元素插入到父元素中
insert(parentElm, vnode.elm, refElm)
}
}
- 執(zhí)行創(chuàng)建component VNode時綁定的init鉤子函數(shù)藻三,創(chuàng)建子組件實例賦值給vnode.componentInstance,并調(diào)用$mount觸發(fā)子組件渲染跪者;
- 調(diào)用initComponent初始化組件棵帽,將子組件實例的根元素節(jié)點$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
// 執(zhí)行init hook生成componentInstance組件實例
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)
}
if (isDef(vnode.componentInstance)) {
// 調(diào)用initComponent初始化組件
initComponent(vnode, insertedVnodeQueue)
// 將vnode.elm插入到DOM Tree中
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
創(chuàng)建子組件實例并觸發(fā)子組件渲染:
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
……
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
保存組件父子關(guān)系渣玲,并調(diào)用子組件構(gòu)造函數(shù)逗概,觸發(fā)子組件_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的子組件的構(gòu)造函數(shù)
return new vnode.componentOptions.Ctor(options)
}
創(chuàng)建vnode時createComponent
構(gòu)建子組件構(gòu)造函數(shù):
- 構(gòu)建子類構(gòu)造函數(shù)柜蜈,獲取propsData仗谆、listeners
- 安裝子類鉤子函數(shù)
- 創(chuàng)建并返回vnode
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | 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
}
子組件初始化,并調(diào)用initInternalComponent
初始化子組件狀態(tài)
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
vm._isVue = true
// merge options
if (options && options._isComponent) {
// 設(shè)置$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淑履,不執(zhí)行掛載隶垮,子組件掛載在父組件創(chuàng)建子組件實例后,手動調(diào)用$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
}
}
執(zhí)行子組件render函數(shù)
Vue.prototype._render = function (): VNode {
const vm: Component = this
// _parentVnode 組件節(jié)點的外殼節(jié)點
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
- 處理子組件時威始,調(diào)用setActiveInstance設(shè)置當前實例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) {
//調(diào)用在平臺index.js中安裝的_patch_方法
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
……
}
restoreActiveInstance()
……
}
沒有舊節(jié)點像街,直接創(chuàng)建新節(jié)點黎棠,此時沒有傳入parentElm
return function patch (oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
// 記錄被插入的vnode隊列晋渺,用于批觸發(fā)insert
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
} else {
……
// 調(diào)用insert鉤子
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}
- 先生成組件實例的elm(即組件跟實例節(jié)點),因為沒有傳入parentElm脓斩,elm并不執(zhí)行插入父級木西,而是子組件全部patch完成后由父組件負責(zé)插入;
- 調(diào)用createChildren遍歷子節(jié)點創(chuàng)建對應(yīng)的DOM節(jié)點随静;
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)) {
// 創(chuàng)建vnode對應(yīng)的DOM元素節(jié)點vnode.elm
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
// 設(shè)置vnode的scope
setScope(vnode)
/* istanbul ignore if */
if (__WEEX__) {
} else {
// 調(diào)用createChildren遍歷子節(jié)點創(chuàng)建對應(yīng)的DOM節(jié)點
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
// 執(zhí)行create鉤子函數(shù)(directives events等的create hook)
// updateAttrs
// updateClass
// updateDomListeners 普通的原生事件
// updateDomProps
// updateStyle
// updateDirectives
invokeCreateHooks(vnode, insertedVnodeQueue)
}
// 將DOM元素插入到父元素中
insert(parentElm, vnode.elm, refElm)
}
} else if (isTrue(vnode.isComment)) {
// 創(chuàng)建注釋節(jié)點vnode.elm八千,并插入到父元素中
vnode.elm = nodeOps.createComment(vnode.text)
insert(parentElm, vnode.elm, refElm)
} else {
// 創(chuàng)建文本節(jié)點vnode.elm,并插入到父元素中
vnode.elm = nodeOps.createTextNode(vnode.text)
insert(parentElm, vnode.elm, refElm)
}
}