组件化思想
js
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;
}
// context.$options._base 在 initGlobalAPI 中定义, 就是 Vue 本身
const baseCtor = context.$options._base;
// plain options object: turn it into a constructor
// 组件局部注册
if (isObject(Ctor)) {
// 相当于调用 Vue.extend,得到一个构造器
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;
}
// 对异步组件的处理
// async component
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.
// 是创建一个注释节点vnode
return createAsyncPlaceholder(asyncFactory, data, context, children, tag);
}
}
data = data || {};
// resolve constructor options in case global mixins are applied after
// component constructor creation
// 构造器配置合并
resolveConstructorOptions(Ctor);
// transform component v-model data into props & events
// 组件的 v-model
if (isDef(data.model)) {
transformModel(Ctor.options, data);
}
// extract props
const propsData = extractPropsFromVNodeData(data, Ctor, tag);
// functional component
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;
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;
// 创建组件 VNode
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ""}`,
data,
undefined,
undefined,
undefined,
context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
);
// Weex specific: invoke recycle-list optimized @render function for
// extracting cell-slot template.
// https://github.com/Hanks10100/weex-native-directive/tree/master/component
/* istanbul ignore if */
if (__WEEX__ && isRecyclableComponent(vnode)) {
return renderRecyclableComponentTemplate(vnode);
}
return vnode;
}
构造子类构造函数
把编写的对象组件转变为一个构造函数
js
// context.$options._base 在 initGlobalAPI 中定义, 就是 Vue 本身
const baseCtor = context.$options._base;
// plain options object: turn it into a constructor
// 组件局部注册
if (isObject(Ctor)) {
// 相当于调用 Vue.extend,得到一个构造器
Ctor = baseCtor.extend(Ctor);
}
extend方法
js
// 使用 Vue 构造器,创建一个"子类",该子类同样支持进一步的扩展
// 扩展时可以传递一些默认配置,就像 Vue 也会有一些默认配置
// 默认配置如果和基类有冲突则会进行选项合并(mergeOptions)
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
// 判断缓存中有没有存在,有就直接使用
// 比如:多次调用 Vue.extend 传入同一个配置项(extendOptions),这时就会启用该缓存
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production' && name) {
// 校验组件名
validateComponentName(name)
}
// 定义 Sub 构造函数,和 Vue 构造函数一致
const Sub = function VueComponent (options) {
// 里面也是和 Vue 构造函数一样,使用 this._init 进行初始化
this._init(options)
}
// 通过寄生组合继承 Vue
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
// 将 Vue 的配置合并到自己的配置里
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = Super
// 将 props 代理到 Sub.prototype._props 对象上
// 在组件内可以通过 this._props 的方式访问
if (Sub.options.props) {
initProps(Sub)
}
// 将 computed 代理到 Sub.prototype 对象上
// 在组件内可以通过 this.computed[key] 的方式访问
if (Sub.options.computed) {
initComputed(Sub)
}
// allow further extension/mixin/plugin usage
// 定义组件的 extend、mixin、use,允许在 Sub 基础上再进一步构造子类
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
// create asset registers, so extended classes
// can have their private assets too.
// 定义 component、filter、directive 三个静态方法
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type]
})
// enable recursive self-lookup
// 如果组件设置了 name 属性,将自己注册到自己的 components 选项中,这也是递归组件的原理
if (name) {
Sub.options.components[name] = Sub
}
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// cache constructor
// 把继承后的 Sub 缓存,好处: 当下次创建 Sub 时,发现已有,就直接使用
cachedCtors[SuperId] = Sub
return Sub
}
采用继承的方式把对象转换为继承自Vue的构造器Sub并返回,做了一些额外的处理比如name,props,computed等,并且进行了缓存。 实例化Sub时,会执行this._init进行Vue实例的初始化。
安装组件钩子函数
VNode在patch流程中对外暴露了各种时机的钩子函数,方便做一些额外的事情。
js
// 将componentVNodeHooks 钩子函数合并到组件data.hook中
function installComponentHooks(data: VNodeData) {
const hooks = data.hook || (data.hook = {});
for (let i = 0; i < hooksToMerge.length; i++) {
const key = hooksToMerge[i];
const existing = hooks[key];
const toMerge = componentVNodeHooks[key];
if (existing !== toMerge && !(existing && existing._merged)) {
hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge;
}
}
}
内置的hooks
js
const componentVNodeHooks = {
init(vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch
// keep-alive 包裹的组件走这里
const mountedNode: any = vnode; // work around flow
// 只调用 prepatch 更新实例属性
componentVNodeHooks.prepatch(mountedNode, mountedNode);
} else {
// createComponentInstanceForVnode 会 new Vue 构造组件实例并赋值到 componentInstance
const child = (vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
));
// 挂载组件-因为在执行_init时,没有el,所以组件自己接管了$mount过程
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
}
},
prepatch(oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
const options = vnode.componentOptions;
const child = (vnode.componentInstance = oldVnode.componentInstance);
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
);
},
insert(vnode: MountedComponentVNode) {
const { context, componentInstance } = vnode;
// 首次渲染,执行的是 mounted 钩子,不会去执行 updated 钩子
if (!componentInstance._isMounted) {
componentInstance._isMounted = true;
callHook(componentInstance, "mounted");
}
if (vnode.data.keepAlive) {
if (context._isMounted) {
// vue-router#1212
// During updates, a kept-alive component's child components may
// change, so directly walking the tree here may call activated hooks
// on incorrect children. Instead we push them into a queue which will
// be processed after the whole patch process ended.
queueActivatedComponent(componentInstance);
} else {
activateChildComponent(componentInstance, true /* direct */);
}
}
},
destroy(vnode: MountedComponentVNode) {
const { componentInstance } = vnode;
if (!componentInstance._isDestroyed) {
if (!vnode.data.keepAlive) {
componentInstance.$destroy();
} else {
deactivateChildComponent(componentInstance, true /* direct */);
}
}
},
};
实例化vnode
js
const name = Ctor.options.name || tag;
// 创建组件 VNode
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ""}`,
data,
undefined,
undefined,
undefined,
context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
);
return vnode;
实例化一个vnode并返回,和普通元素的vnode不同,组件的vnode没有children。
patch中createComponent
js
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data // 这个是 组件的 VNodeData
if (isDef(i)) {
// isReactivated 用来判断组件是否缓存。
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {
// 执行组件初始化的内部钩子 init
i(vnode, false /* hydrating */ )
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm) // 插入顺序:先子后父
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
vnode是组件VNode时,i就是init钩子,init钩子函数调用 createComponentInstanceForVnode创建Vue实例。
js
export function createComponentInstanceForVnode(
vnode: any, // we know it's MountedComponentVNode but flow doesn't
// parent:是 activeInstance, 在 lifecycle 中
parent: any // activeInstance in lifecycle state
): Component {
const options: InternalComponentOptions = {
_isComponent: true,
_parentVnode: vnode,
parent,
};
// check inline-template render functions
const inlineTemplate = vnode.data.inlineTemplate;
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render;
options.staticRenderFns = inlineTemplate.staticRenderFns;
}
// 执行 vue 子组件实例化
return new vnode.componentOptions.Ctor(options);
}
vnode.componentOptions.Ctor是构造子类构造函数的Sub,相当于new Sub(options).在执行实例的初始化方法时,组件则会调用initInternalComponent方法。
js
// 组件的配置合并
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
组件$mount方法之后执行的render方法中,将当前组件渲染的vnode的parent执行父VNode_parentVnode,建立父子关系。在update方法中,
js
vm._vnode.parent === vm.$vnode
在$mount之前会调用initLifecycle(vm)方法。
js
export function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
// ...
}
vm.parent用来保留当前vm的父实例,将当前vm存储到父实例的children中。在update过程中,使用prevActiveInstance保留父实例。在完成固件patch之后,如果组件patch过程中又创建了子组件,DOM的插入顺序是先子后父。
总结:
组件化创建三个关键的步骤是:构造子类构造函数、安装组件钩子函数和实例化vnode。因为组件被转化为继承自Vue的子类构造函数,在初始化过程中也是调用_init方法,经过render方法创建虚拟DOM,通过patch和createElm创建真实DOM,和使用普通元素有一些区别,特别是父子关系的处理,在DOM插入时深度遍历,递归调用,先子后父。