_render函數(shù)
- 上一講我們分析到調(diào)用了
vm._update(vm._render(), hydrating),那么這一講我們就先分析 vm._render() 做了什么 - 首先 _render 方法是在哪里定義的呢?在
core/instance/render.js中
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
}
vm.$vnode = _parentVnode
let vnode
try {
currentRenderingInstance = vm
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
vnode = vm._vnode
} finally {
currentRenderingInstance = null
}
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
if (!(vnode instanceof VNode)) {
vnode = createEmptyVNode()
}
vnode.parent = _parentVnode
return vnode
}
我們可以看到
const { render, _parentVnode } = vm.$options先從 $options 中拿到了render然后執(zhí)行了 render,并傳參 vm.$createElement, 從而獲取到 vnode 并返回
// vm._renderProxy就是vm,忽略不分析
vnode = render.call(vm._renderProxy, vm.$createElement)
vnode
- dom元素是非常龐大的,我們頻繁去做dom更新就會(huì)產(chǎn)生一定的性能問題
- Vnode 就是用原生的 js 對(duì)象去描述 DOM 節(jié)點(diǎn),它借鑒了開源庫(kù)snabbdom ,在 flow 文件夾下可以看到 VNode 的定義,,對(duì) vnode 定義了一些關(guān)鍵屬性如標(biāo)簽名、數(shù)據(jù)、子節(jié)點(diǎn)等,用來映射到真實(shí) DOM 的渲染,因此輕量簡(jiǎn)單
- Vnode 映射到真實(shí) DOM 需要經(jīng)歷創(chuàng)建 diff patch等過程
- Vnode 是如何創(chuàng)建的呢? 就是通過我們之前分析的 vm.$createElement 函數(shù)
vm.$createElement 是什么
- 在 function Vue 階段做了各種 mixin , 其中有 initRender, initRender 中包括這樣一段代碼
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
- 也就是說我們的 vm.$createElement 實(shí)際上是去執(zhí)行了 createElement 操作
createElement
- 在
core/vdom/create-element.js中
export function createElement (
context: Component, // vm 實(shí)例
tag: any, // 標(biāo)簽
data: any, // 數(shù)據(jù)
children: any, // 子節(jié)點(diǎn),從而構(gòu)建出 vnode tree
normalizationType: any,
alwaysNormalize: boolean
): VNode | Array<VNode> {
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children
// 沒有 data 時(shí)參數(shù)前移
children = data
data = undefined
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE
}
return _createElement(context, tag, data, children, normalizationType)
}
- 我們發(fā)現(xiàn)它實(shí)際上是對(duì)參數(shù)做了一層封裝,然后調(diào)用 _createElement 方法
_createElement
- 我把這部分的代碼精簡(jiǎn)了一下,保留了核心的部分
export function _createElement (
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
vnode = createComponent(Ctor, data, context, children, tag)
} else {
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
vnode = createComponent(tag, data, context, children)
}
if (Array.isArray(vnode)) {
return vnode
} else if (isDef(vnode)) {
if (isDef(ns)) applyNS(vnode, ns)
if (isDef(data)) registerDeepBindings(data)
return vnode
} else {
return createEmptyVNode()
}
}
- 我們會(huì)發(fā)現(xiàn)這個(gè)函數(shù)先對(duì) children 做了 normalize, 因?yàn)閏hildren 可能是數(shù)組類型,normalize方法,normalizeArrayChildren 主要的邏輯是遍歷 children,獲得單個(gè)節(jié)點(diǎn) c, 然后判斷 c 的類型,如果是數(shù)組類型,則遞歸調(diào)用 normalizeArrayChildren,如果是基礎(chǔ)類型,則通過 createTextVNode 轉(zhuǎn)化為 VNode 類型(其中做了優(yōu)化,兩個(gè)連續(xù)的 text 節(jié)點(diǎn)會(huì)合并成一個(gè) text 節(jié)點(diǎn)),變成了一個(gè)一維數(shù)組
- 我們現(xiàn)在 demo 的 tag 是 string 類型,因此會(huì)走到 vnode = new Vnode()從而生成一個(gè) vnode
demo 調(diào)試
- 調(diào)用 _createElement

- _createElement 創(chuàng)建了vnode

- 因此 _render 最終返回了一個(gè) vnode
