组件化机制
1、vue.component()
==任何的全局api 都在global-api目录下==
目录位置:vue\src\core\global-api\assets.js
1、初始化调用 initAssetRegisters()
export function initAssetRegisters (Vue: GlobalAPI) {
/**
* Create asset registration methods.
*/
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
// 这里用正则判断组件的命名
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
// 定义组件化方法的处理
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
this.options[type + 's'][id] = definition
return definition
}
}
})
}
以上方法做的事情是初始化时给Vue挂上components和directive方法
具体分析
// Vue.components('my-com', {template: '<div>123</div>'})
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
definition = this.options._base.extend(definition)
}
很迷惑 this.options._base 是什么
在global-api的index.js 有这样的一行代码,所以
Vue.options._base = Vue
// this.options._base.extend(definition) 等同于 Vue.extend(definition)
2、针对Vue.extends()研究
目录位置:src\core\global-api\extend.js
当前主要是return了一个组件的构造函数
Vue.extend = function (extendOptions: Object): Function {
extendOptions = 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
if (process.env.NODE_ENV !== 'production' && name) {
validateComponentName(name)
}
// 和原本的vue一样的初始化方法
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
// For props and computed properties, we define the proxy getters on
// the Vue instances at extension time, on the extended prototype. This
// avoids Object.defineProperty calls for each instance created.
if (Sub.options.props) {
initProps(Sub)
}
if (Sub.options.computed) {
initComputed(Sub)
}
// allow further extension/mixin/plugin usage
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
// create asset registers, so extended classes
// can have their private assets too.
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type]
})
// enable recursive self-lookup
//组件自调用注册自己
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
cachedCtors[SuperId] = Sub
return Sub
}
}
3、何时调用
我们先来看看render执行的函数
==render函数是 通过template解析会生成以下字符数再通过new Function('这段字符串')生成函数==
with(this){return _c('div',{attrs:{"id":"demo"}},[
_c('h1',[_v("虚拟DOM")]),_v(" "),
_c('p',[_v(_s(foo))]),_v(" "),
_c('comp') // 对于组件的处理并⽆特殊之处
],1)}
解惑: _c 是什么
在 初始化的时候 执行 initRender()的时候
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// 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)
目前的已知的执行顺序是
new Vue() => $mount() => vm._render() => createElement() => _createElement(context, tag, data, children, normalizationType)
4、解析_createElement函数
目录文件 src\core\vdom\create-element.js
标准化处理
// 生成vnode
export function _createElement (
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
// 有data被响应返回空
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 // 处理is属性
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
//tag不存在
if (!tag) {
// in case of component :is set to falsy value
return createEmptyVNode()
}
// 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
}
// children的标准化处理
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
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
// resolveAsset 动态加载
} 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()
}
}
createElement 针对普通标签的创建、针对组件、针对动态引入的组件创建
5、针对createComponent(Ctor, data, context, children, tag)研究
该方法返回一个vnode
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
}
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
}
// 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.
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
// 异步组件这里就返回占位符了
data = data || {}
// resolve constructor options in case global mixins are applied after
// component constructor creation
resolveConstructorOptions(Ctor) // 将options合并
// 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
// // tag?: string,data?: VNodeData,children?: ?Array<VNode>,text?: string, elm?: Node,context?: Component,componentOptions?: VNodeComponentOptions,asyncFactory?: Function
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
return vnode
}
这里 createComponent 返回的只是vnode。得到虚拟dom
6、针对 installComponentHooks(data)
存在四个钩子,区别去生命周期的钩子
const componentVNodeHooks = {
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
// ...
},
prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
// ...
},
insert (vnode: MountedComponentVNode) {
// ...
},
destroy (vnode: MountedComponentVNode) {
// ...
}
}
对于init钩子什么时候被调用
root.$mount() => patch => createElm() => createComponent() => init()
==执行以下函数才会真正的实例化==
function init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch keptalive的处理
const mountedNode: any = vnode // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode)
} else {
// 真正的实例化
// vnode.componentInstance 虚拟dom保存组件实例
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
// 执行挂载
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
备注: createComponentInstanceForVnode 解析一下
function createComponentInstanceForVnode (
vnode: any,
// activeInstance in lifecycle state
parent: any
) {
return new vnode.componentOptions.Ctor(options)
}
在patch过程中遇到组件标签创建组件的时候 createComponent调用 init 会获取组件实例
通过 new vnode.componentOptions.Ctor(options) 会生成vueComponent对象但dom未挂载
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 */)
}
// 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
}
}
}