
一句話簡(jiǎn)述:
render函數(shù)的職責(zé)就是把模板解析成Vnode(虛擬DOM)節(jié)點(diǎn)
具體是如何做的?
可以分解成幾個(gè)問題:
1.模板編譯的輸出結(jié)果是什么?
模板編譯會(huì)將用戶寫的模板↓
<div id="NLRX"><p>Hello {{name}}</p></div>
轉(zhuǎn)換成用層級(jí)對(duì)象表達(dá)的ast
ast = {
'type': 1,
'tag': 'div',
'attrsList': [
{
'name':'id',
'value':'NLRX',
}
],
'attrsMap': {
'id': 'NLRX',
},
'static':false,
'parent': undefined,
'plain': false,
'children': [{
'type': 1,
'tag': 'p',
'plain': false,
'static':false,
'children': [
{
'type': 2,
'expression': '"Hello "+_s(name)',
'text': 'Hello {{name}}',
'static':false,
}
]
}]
}
因?yàn)楸疚牡闹攸c(diǎn)不是模板編譯,所以這里的轉(zhuǎn)換過程略,但我推薦你
https://vue-js.com/learn-vue/complie/
可以解答你的疑惑。
render函數(shù)實(shí)際上做了兩件事
1.將ast生成render函數(shù)
with(this){
reurn _c(
'div',
{
attrs:{"id":"NLRX"},
}
[
_c('p'),
[
_v("Hello "+_s(name))
]
])
}
不用急,函數(shù)內(nèi)容(with(this){....})的形成會(huì)在后面講到。
再說明一點(diǎn),你可能會(huì)奇怪render函數(shù)為什么要先
with(this){
//函數(shù)執(zhí)行
}
這其實(shí)是一種特殊語(yǔ)法,在with內(nèi)部的函數(shù)執(zhí)行環(huán)境會(huì)切換為this,_c明明沒作為參數(shù)傳進(jìn)函數(shù)體內(nèi)部,但為何能調(diào)用?因?yàn)閠his上有_c方法,而函數(shù)內(nèi)部環(huán)境又是this所以能直接調(diào)用,而這個(gè)this是什么?實(shí)際上就是vue實(shí)例vm啦!
2.運(yùn)行render函數(shù),生成vnode節(jié)點(diǎn)
中間函數(shù)會(huì)多次調(diào)用_c,也就是createElemnt來生成vnode,
_c的作用是根據(jù)用戶輸入的參數(shù)來生成相應(yīng)的vnode節(jié)點(diǎn),官方api文檔中createElement的用法如下,是不是和上面render函數(shù)內(nèi)的內(nèi)容很像,實(shí)際上就是一回事。

PS:vnode長(zhǎng)什么樣子?如下,這就是render最后執(zhí)行的結(jié)果
<div id='a'><span>難涼熱血</span></div>
// VNode節(jié)點(diǎn)
{
tag:'div',
data:{},
children:[
{
tag:'span',
text:'難涼熱血'
}
]
}
第一件事,如何生成render函數(shù)?
前人已經(jīng)講得很完備了,直接貼出來
https://vue-js.com/learn-vue/complie/codegen.html#_1-%E5%89%8D%E8%A8%80
第二件事,如何執(zhí)行render函數(shù)?
//源碼位置:src/code/instance/render.js
function initRender (vm: Component) {
vm._vnode = null // the root of the child tree
vm._staticTrees = null // v-once cached trees
const options = vm.$options
const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
// bind the createElement fn to this instance
// so that we get proper render context inside i t.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) //mark2:用戶創(chuàng)建vnode,將rende內(nèi)的字符串整成vnode模板
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated
const parentData = parentVnode && parentVnode.data
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
... 這里不重要
}
}
在initRender中vue為vue的實(shí)例vm定義了_c函數(shù),
這就為渲染函數(shù)中要調(diào)用的_c埋下了伏筆。
但是這里實(shí)際上還不是真正定義render函數(shù)的位置,那么真正定義render函數(shù)的位置在哪里的?
在render.js文件中,滾輪繼續(xù)向下翻我們發(fā)現(xiàn)了這個(gè)↓
function renderMixin (Vue: Class<Component>) {
......省去不重要邏輯
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
// render self
let vnode
try {
currentRenderingInstance = vm
vnode = render.call(vm._renderProxy, vm.$createElement) //mark:從with函數(shù)變成真正的vnode,renderProxy不知道是什么,但他可以調(diào)動(dòng)提供_c,然后c會(huì)調(diào)用vm.$createEle大概
//參數(shù) vm.$createElement在一般情況下不需要,特殊情況暫時(shí)不知
} catch (e) {
...
} finally {
...
}
// if the returned array contains only a single node, allow it
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
// return empty vnode in case the render function errored out
if (!(vnode instanceof VNode)) {
vnode = createEmptyVNode()
}
// set parent
vnode.parent = _parentVnode
return vnode
}
}
觀察 vnode = render.call(vm._renderProxy, vm.$createElement)就是調(diào)用render函數(shù)的地方,并生成了vnode最后返回vnode節(jié)點(diǎn)
在此之后,vnode利用diff算法就可以完成對(duì)頁(yè)面的數(shù)據(jù)更新了。
執(zhí)行render的過程就是將_c執(zhí)行,補(bǔ)充_c返回的vnode對(duì)象的過程
with(this){
reurn _c(
'div',
{
attrs:{"id":"NLRX"},
}
[
_c('p'),
[
_v("Hello "+_s(name))
]
])
}
那么非常感謝你看到這里,那么最后我們?cè)俳忉屢幌耞c也就是createElement相信你就能完全弄明白整個(gè)render的過程了!
以下是createElement的源碼:
export function createElement (
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any,
alwaysNormalize: boolean
): VNode | Array<VNode> {
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children
children = data
data = undefined
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE
}
return _createElement(context, tag, data, children, normalizationType)
}
第一層createElement只是個(gè)中間層,它實(shí)際上是為了調(diào)用_createElement而做了一些中間處理
export function _createElement (
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
if (isDef(data) && isDef((data: any).__ob__)) {
process.env.NODE_ENV !== 'production' && warn(
`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
'Always create fresh vnode data objects in each render!',
context
)
return createEmptyVNode()
}
// object syntax in v-bind
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if (!tag) {
// in case of component :is set to falsy value
return createEmptyVNode()
}
// warn against non-primitive key
if (process.env.NODE_ENV !== 'production' &&
isDef(data) && isDef(data.key) && !isPrimitive(data.key)
) {
if (!__WEEX__ || !('@binding' in data.key)) {
warn(
'Avoid using non-primitive value as key, ' +
'use string/number value instead.',
context
)
}
}
// support single function children as default scoped slot
if (Array.isArray(children) &&
typeof children[0] === 'function'
) {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
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)) {
// platform built-in elements
if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) {
warn(
`The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
context
)
}
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
// direct component options / constructor
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()
}
}
而對(duì)_createElement大致掃過一眼,最后return的是vnode節(jié)點(diǎn),這就是_createElement的作用對(duì)比render內(nèi)部來看:
根據(jù)環(huán)境(context,一般是vm),標(biāo)簽(tag:div),data: attrs:{"id":"NLRX"},和 children(子節(jié)點(diǎn))來生成vnode
with(this){
reurn _c(
'div',
{
attrs:{"id":"NLRX"},
}
[
_c('p'),
[
_v("Hello "+_s(name))
]
])
}