细读Vue2.6.14 Core 源码(8): $createElement
/**
@date 2021-10-22
@description 细读Vue2.6.14 Core 源码(8): $createElement
*/
壹(序)
在一个Vue项目的main文件中,一般都会这样去开始整个Vue项目的生命周期
new Vue({
render: h => h(App),
}).$mount('#app')
其中的render很容易理解,就是去渲染App组件,而其中的h就是渲染的关键函数,这个h函数是在Vue.prototype._render中加上去的
vnode = render.call(vm._renderProxy, vm.$createElement)
vm.$createElement在initRender函数中进行声明的
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
其中与 vm.$createElement 类似的还有一个 _c 函数,只是最后的一个参数不同,_c函数一般是在解析后的.vue文件中使用,比如一个简单的App.vue在解析后会变成
var render = function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c(
"div",
{ attrs: { id: "app" } },
[_c("HelloWorld", { attrs: { msg: "Welcome to Your Vue.js App" } })],
1
)
}
var staticRenderFns = []
render._withStripped = true
export { render, staticRenderFns }
贰(createElement)
从 $createElement 和 _c 两个函数中可以看到, createElement才是重中之重
/**
* 在 new Vue({ render: h => h(App) }).$mount('#app') => mountComponent => vm._render => vm.$createElement 会调用createElement
* 其他组件的 $mount 在 componentVNodeHooks 中的 init 中调用
* Vue将组件编译完成后,会生成render函数,此render函数依然在 vm._render 中调用
* 编译生成的render函数不会用到 vm.$createElement 而是使用 vm._c(没有_c时会使用vm.$createElement)
* vm.$createElement 与 vm._c 的区别在于 createElement 函数的最后一个参数 alwaysNormalize
*/
export function createElement(
context: Component, // 当前操作的vm实例
tag: any, // 标签名
data: any, // 标签属性 ? 标签属性 : 子节点
children: any, // 标签属性 ? 子节点 : undefined
normalizationType: any, //
alwaysNormalize: boolean
): VNode | Array<VNode> {
// data是Array(说明没有标签属性,data是子节点
// TODO: isPrimitive 的场景暂时不知道
if (Array.isArray(data) || isPrimitive(data)) {
// 这种情况下 children 基本是 undefined,就是相当于将 normalizationType 赋值为undefined
normalizationType = children;
// children就应该是data
children = data;
// data为undefined
data = undefined;
}
// 根据 alwaysNormalize 设置 normalizationType
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE;
}
// 真实的是调用 _createElement 函数
return _createElement(context, tag, data, children, normalizationType);
}
叁(_createElement)
createElement函数中,最终其实是调用_createElement函数,这里会生成标签或组件的VNode
// 从 text vnode 开始生成,再往外生成,直到最外层
// 比如
/**
从 text 开始生成,再往外生成,直到最外层
比如
<h1>
<span>hello world1 </span>
</h1>
是先生成 hello world1 的vnode(不是调用_createElement函数来生成)再生成span(会调用_createElement函数生成)
再生成 h1
*/
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__)) {
// data如果支持了响应式,则在非生产环境下报警告
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
);
// 创建一个空vnode并返回
return createEmptyVNode();
}
// object syntax in v-bind
// 动态组件
// <component is="HelloWorld"></component>编译过后的调用到此处时
// tag就是'HelloWorld',data中会包含{ tag: component }
if (isDef(data) && isDef(data.is)) {
tag = data.is;
}
// 无tag的情况,返回空vnode
if (!tag) {
// in case of component :is set to falsy value
return createEmptyVNode();
}
// warn against non-primitive key
// key是一个object类型的情况,报警告
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;
}
// 规范化的类型
// SIMPLE_NORMALIZE 就是简单的把children打平
// ALWAYS_NORMALIZE 不仅将children打平,同时还会将children中的primitive数据转化为text node
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children);
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children);
}
let vnode, ns;
// 正常生成vnode时,只传一个标签名称,div/span等
// 但是第一个$mount调用时,一般是<App></App>
if (typeof tag === "string") {
let Ctor;
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag);
// 是否是保留tag,HTML的tag或svg
if (config.isReservedTag(tag)) {
// platform built-in elements
// 只允许 .native 在自建组件上使用
if (
process.env.NODE_ENV !== "production" &&
isDef(data) &&
isDef(data.nativeOn) &&
data.tag !== "component"
) {
warn(
`The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
context
);
}
// 生成VNode
vnode = new VNode(
config.parsePlatformTagName(tag), // 获取tagName
data,
children,
undefined,
undefined,
context
);
} else if (
(!data || !data.pre) &&
isDef((Ctor = resolveAsset(context.$options, "components", tag)))
) {
// 生成组件的 vnode
vnode = createComponent(Ctor, data, context, children, tag);
} else {
// 其他情况
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();
}
}
肆(createComponent)
创建组件又需要一番操作
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)
}
// if at this stage it's not a constructor or an async component factory,
// reject.
if (typeof Ctor !== 'function') {
if (process.env.NODE_ENV !== 'production') {
warn(`Invalid Component definition: ${String(Ctor)}`, context)
}
return
}
// 异步组件
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
if (Ctor === undefined) {
// return a placeholder node for async component, which is rendered
// as a comment node but preserves all the raw information for the node.
// the information will be used for async server-rendering and hydration.
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
// data
data = data || {}
// 解析构造函数options
resolveConstructorOptions(Ctor)
// 处理应用在组件上的 v-model
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
// 获取props
const propsData = extractPropsFromVNodeData(data, Ctor, tag)
// 函数组件
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners = data.on
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn
// abstract 为 true
if (isTrue(Ctor.options.abstract)) {
// abstract components do not keep anything
// other than props & listeners & slot
// work around flow
const slot = data.slot
data = {}
if (slot) {
data.slot = slot
}
}
// 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
)
if (__WEEX__ && isRecyclableComponent(vnode)) {
return renderRecyclableComponentTemplate(vnode)
}
return vnode
}
终(导航)
细读Vue2.6.14 Core 源码(2): After Vue
细读Vue2.6.14 Core 源码(3): initGlobalAPI
细读Vue2.6.14 Core 源码(5): initState
细读Vue2.6.14 Core 源码(6): defineReactive