组件化与渲染函数底层实现
Vue组件化通过 Vue.extend 创建子类,渲染函数执行返回VNode树,经 patch 挂载到真实DOM。
Vue.extend 组件子类化
JavaScript
Vue.extend = function(extendOptions) {
const Super = this
const SuperId = Super.cid
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) return cachedCtors[SuperId]
const name = extendOptions.name || Super.options.name
const Sub = function VueComponent(options) {
this._init(options)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
Sub.options = mergeOptions(Super.options, extendOptions)
Sub['super'] = Super
// 继承全局API
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
return Sub
}
组件注册时创建Vue子类,继承父类原型和全局API。
组件实例化流程
JavaScript
// 父组件渲染函数
function render() {
return _c('child-component', { props: { msg: 'hello' } })
}
// createElement 处理组件
function createComponent(Ctor, data, context, children, tag) {
// Ctor是Vue构造函数
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
return vnode
}
createElement 识别组件时创建组件VNode,存储构造函数引用。
组件VNode创建与挂载
JavaScript
// patch过程
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
const i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
// 创建组件实例
i.hook.init(vnode)
if (isDef(vnode.componentInstance)) {
// 调用子组件mount
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm)
return true
}
}
}
// init钩子
init(vnode) {
const child = vnode.componentInstance = createComponentInstanceForVnode(vnode)
child.$mount(undefined)
}
组件VNode patch时调用 init 钩子创建实例并挂载。
渲染函数执行
JavaScript
// 组件渲染函数
function render() {
with(this) {
return _c('div', { attrs: { id: 'app' } }, [
_c('h1', [_v(_s(title))]),
_c('p', [_v(_s(content))])
])
}
}
// 执行返回VNode树
{
tag: 'div',
data: { attrs: { id: 'app' } },
children: [
{ tag: 'h1', children: [{ text: '标题' }] },
{ tag: 'p', children: [{ text: '内容' }] }
]
}
渲染函数通过 with(this) 绑定组件实例,调用辅助函数构建VNode树。
patch 挂载过程
JavaScript
Vue.prototype._update = function(vnode, hydrating) {
const vm = this
const prevEl = vm.$el
const prevVnode = vm._vnode
vm._vnode = vnode // 更新当前VNode
if (!prevVnode) {
// 首次渲染
vm.$el = vm.__patch__(vm.$el, vnode, hydrating)
} else {
// 更新
vm.$el = vm.__patch__(prevVnode, vnode)
}
if (prevEl) prevEl.parentNode?.removeChild(prevEl)
}
_update 对比新旧VNode,首次渲染调用 __patch__ 挂载DOM。
辅助函数映射
JavaScript
// 渲染函数辅助函数
{
'_c': (tag, data, children) => createElement(tag, data, children),
'_v': text => createTextVNode(text),
'_e': () => createEmptyVNode(),
'_s': val => toString(val),
'_l': (list, fn) => renderList(list, fn),
'_t': (name, props) => renderSlot(name, props)
}
辅助函数封装VNode创建逻辑,简化渲染函数代码。
要点总结
Vue.extend创建Vue子类继承原型和全局API,缓存构造函数- 组件识别时
createElement创建组件VNode存储构造函数引用 - 组件VNode patch时调用
init钩子创建实例并$mount - 渲染函数通过
with(this)绑定实例,调用辅助函数构建VNode树 _update对比新旧VNode,首次渲染调用__patch__挂载- 辅助函数
_c/_v/_e/_s/_l/_t封装VNode创建逻辑
📝 发现内容有误?点击此处直接编辑