time: 2021-05-06 16:04:26
author: heyunjiang
普通组件和函数式组件都总结在这篇文章中了
context.data
是所有 attribute 和事件的统一集合对于如下组件,2者是如何编译的呢?
<hello /> // 普通组件
<world /> // 函数式组件
// 编译结果
[
createElement('hello'),
createElement('world')
]
// 待补充函数式组件的编译例子
export default {
functional: true,
render: function(h, context) {
return (
<div></div>
)
}
}
// 编译结果
var lib_vue_loader_options_srcvue_type_script_lang_js_ = ({
functional: true,
render: function render(h, context) {
return h("div");
}
});
函数式组件的 jsx 或 template 编译结果还是生成 render 方法,此刻依然使用 createElement 代替了标签; createElement 在执行时,也是调用的 createComponent 方法
if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
vnode = createComponent(Ctor, data, context, children, tag)
}
此刻已经获取到函数式组件的配置对象 Ctor 了,来看看 createComponent 方法
const baseCtor = context.$options._base
if (isObject(Ctor)) {
// Vue.extend 表示合并新的配置,生成新的 Vue 函数对象,此刻未实例化
Ctor = baseCtor.extend(Ctor)
}
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// 为普通组件注入 component hooks.init 等方法
installComponentHooks(data)
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
return vnode
如果 functional === true,内部调用 createFunctionalComponent 来生成函数式组件;普通组件则直接 new vnode
export function createFunctionalComponent (
Ctor: Class<Component>,
propsData: ?Object,
data: VNodeData,
contextVm: Component,
children: ?Array<VNode>
) {
const options = Ctor.options
// 1. 生成 render context 对象
const renderContext = new FunctionalRenderContext(
data,
props,
children,
contextVm,
Ctor
)
// 2. 同样调用 render 生成 vnode 对象
const vnode = options.render.call(null, renderContext._c, renderContext)
if (vnode instanceof VNode) {
// 3. 标记为函数式 vnode
return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext)
}
}
createComponent 函数式组件和普通组件处理对比
组件 slot 渲染请查阅 插槽渲染
问题:普通组件内部的 render 方法是在什么时候执行,新生成的 vnode 的 parent 是谁?
答:普通组件的 render 是在 mountComponent 中调用 vm._render 执行;新生成的 vnode 的 parent 是会在组件 initLifecycle 时绑定父 vnode
在调用 createElement 生成 vnode 之后,父组件 watcher 通过 vm._update 去渲染 vnode,来看看渲染对 普通组件 的处理
处理流程如下: patch -> createElm -> createComponent,而 createComponent 内部调用 vnode.data.hooks.init (是在 vdom/create-component/componentVNodeHooks 中定义的)来初始化组件对象
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)
}
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
来看看 vdom/create-component/componentVNodeHooks
const componentVNodeHooks = {
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive 组件更新
const mountedNode: any = vnode
componentVNodeHooks.prepatch(mountedNode, mountedNode)
} else {
// 所有组件初次渲染,实例化组件
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance // 表示当前渲染组件的环境
)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
}
export function createComponentInstanceForVnode (
vnode: any,
parent: any
): Component {
const options: InternalComponentOptions = {
_isComponent: true,
_parentVnode: vnode,
parent
}
return new vnode.componentOptions.Ctor(options)
}
可以看到
而函数式组件的渲染,是在父组件 patch 中直接渲染了,而不会生成自己的 vm 实例再渲染
函数式组件只是一层壳子,没有独立 vue 实例,也就是说没有生成额外的对象,占用内存少了,自然就性能更好。
函数式组件使用场景
问题
2021-05-06 19:32:48
vue 组件核心的是 createElement 生成 vnode,然后调用 patch 去渲染成真实 dom,而且这2块分别属于 src/core/vdom/create-element
和 src/core/vdom/patch
模块,再次归纳一下 vue2 的核心模块
vdom
下包含了 createElement 和 patch 2大核心,涉及到 vnode 的生成和渲染真实 domobserver
模块实现了响应式系统,内部实现了 watcher, dep, scheduler 核心对象,也作为 vue 实例和 vdom 的衔接点src/core/instance/lifecycle/
的 mountComponent 方法中,使用到了 watcher 对象。updateComponent 作为 watcher 的更新方法,又调用了 vm._update(vm._render())
结构instance
: 生成 vm 实例,实现了 data、injections、provide 数据管理、自定义事件管理、lifecycle 渲染 dom、render 生成 vnode 模块;
其中 lifecycle 定义了 vm._update 渲染 dom,render 定义了 vm._render 生成 vnode 对象,这2个核心衔接模块,由 watcher 统一调度,包括初次渲染和后续更新总结