问题1组件怎么注册
问题2组件注册后,怎么实例化
<!DOCTYPE html>
<html>
<head>
<title>Vue源码剖析</title>
<script src="../../dist/vue.js"></script>
</head>
<body>
<div id="demo">
<h1>Vue组件化机制</h1>
<comp></comp>
</div>
<script>
Vue.component('comp', {
template: '<div>I am comp</div>'
})
// 创建实例
const app = new Vue({
el: '#demo'
});
console.log(app.$options.render);
</script>
</body>
</html>
观察⽣成的渲染函数
"with(this){return _c('div',{attrs:{"id":"demo"}},[
_c('h1',[_v("虚拟DOM")]),_v(" "),
_c('comp') // 对于组件的处理并⽆特殊之处
],1)}"
问题1
src/core/index.js
import Vue from './instance/index'
// 1.初始化全局API:component/filter/directive/use/mixin/util/extend
initGlobalAPI(Vue)
export function initGlobalAPI (Vue: GlobalAPI) {
// config
const configDef = {}
configDef.get = () => config
if (process.env.NODE_ENV !== 'production') {
configDef.set = () => {
warn(
'Do not replace the Vue.config object, set individual fields instead.'
)
}
}
Object.defineProperty(Vue, 'config', configDef)
// exposed util methods.
// NOTE: these are not considered part of the public API - avoid relying on
// them unless you are aware of the risk.
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
// 2.6 explicit observable API
Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
}
Vue.options = Object.create(null)
// 遍历数组:components/filters/directives
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents)
initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)
}
重点是下面
Vue.options = Object.create(null)
// 遍历数组:components/filters/directives
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue
initAssetRegisters(Vue)
export function initAssetRegisters (Vue: GlobalAPI) {
/**
* Create asset registration methods.
*/
// ['component','filter','directive']
ASSET_TYPES.forEach(type => {
// 声明静态的方法 Vue.component = function() {}
// Vue.component('comp', {})
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)) {
// name设置
definition.name = definition.name || id
// Vue.extend({}) => 组件构造函数 Ctor
// new Ctor()
// 将传入的组件配置对象转换为组件构造函数
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
// 向全局的选项中加入全局组件配置对象
// components[id] = Ctor
// VueComponent
this.options[type + 's'][id] = definition
return definition
}
}
})
}
这里做了两件事:
1是给传入的配置项重新覆盖成extend构造函数,
2是在this.$options[type+'s'][id] = definition,向全局的选项中加入全局组件配置对象
- 给Vue设置空的
component/filter/directive
- 通过
initAssetRegisters
设置component
和directive
的构造函数 - 向全局的选项中加入全局组件配置对象
components[id] = Ctor this.options[type + 's'][id] = definition
; 这里就是我们可以用Vue.Components
去配置组件了
_init初始化时会合并选项我们的组件总才有了那些全局组件
至此组件的注册清楚了
====================================================================
initMixin中的_init方法
初始化流程的时候会有 初始化核心逻辑
// 初始化核心逻辑
vm._self = vm
initLifecycle(vm) // $parent/$root/...
initEvents(vm) // 自定义事件监听
initRender(vm) // $slots/$createElement
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm) // props/methods/data/computed/watch
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
export function initRender (vm: Component) {
vm._vnode = null // the root of the child tree
vm._staticTrees = null // v-once cached trees
const options = vm.$options
const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
// 由编译器生成的渲染函数调用_c
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// 其实是render函数中的参数h
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated
const parentData = parentVnode && parentVnode.data
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
!isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
}, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
!isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
}, true)
} else {
// 定义$attrs和$listeners响应式
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
}
// 其实是render函数中的参数h
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
就是我们平时的render函数里的h
1. 这里的initRender初始化了$slots
和$createElement
定义$attrs
和$listeners
响应式
接着看createElement
export function createElement (
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any,
alwaysNormalize: boolean
): VNode | Array<VNode> {
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children
children = data
data = undefined
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE
}
return _createElement(context, tag, data, children, normalizationType)
}
// 将传入的组件配置转换为VNode
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__)) {
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
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if (!tag) {
// in case of component :is set to falsy value
return createEmptyVNode()
}
// warn against non-primitive key
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
}
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)
// 判断是否是保留标签:div p span
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 = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} 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()
}
}
2.createElment
- 如果是原生标签就直接
new Vnode()
- 如果定义了vue构造函数
isDef(Ctor = resolveAsset(context.$options, 'components', tag)
就直接createComponent(Ctor, data, context, children, tag)
(这里的构造函数就是在注册阶段通过extend返回的构造函数)
接着看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
}
const baseCtor = context.$options._base
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
// install component management hooks onto the placeholder node
// 安装组件管理钩子
installComponentHooks(data)
// return a placeholder vnode
const name = Ctor.options.name || tag
// vue-component-1-comp
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
3. createComponent
- 安装组件管理钩子
installComponentHooks(data)
- 生成组件对应的vnode(组件名
vue-component-${Ctor.cid}${name ?
-${name}: ''}
)
接着看installComponentHooks
// 组件管理钩子定义
const componentVNodeHooks = {
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch
const mountedNode: any = vnode // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode)
} else {
//
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
// 挂载
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
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 */)
}
}
}
}
const hooksToMerge = Object.keys(componentVNodeHooks)
// 管理钩子:合并用户编写的钩子和系统默认钩子
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
}
}
}
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
export function createComponentInstanceForVnode (
vnode: any, // we know it's MountedComponentVNode but flow doesn't
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
}
// 创建自定义组件实例
return new vnode.componentOptions.Ctor(options)
}
installComponentHooks给组件提供了init钩子中组件的构造函数被调用了创建自定义组件实例
init函数什么时候执行?
应该在patch的时候执行:为什么
老爹执行parent.$mount =>_render( ) => _update() =>patch() => createElm()
patch.js
// create new node
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// 递归创建元素或组件
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
if (isDef(vnode.elm) && isDef(ownerArray)) {
vnode = ownerArray[index] = cloneVNode(vnode)
}
vnode.isRootInsert = !nested // for transition enter check
// 自定义组件创建过程
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
// 原生标签创建
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
if (isDef(tag)) {
if (process.env.NODE_ENV !== 'production') {
if (data && data.pre) {
creatingElmInVPre++
}
if (isUnknownElement(vnode, creatingElmInVPre)) {
warn(
'Unknown custom element: <' + tag + '> - did you ' +
'register the component correctly? For recursive components, ' +
'make sure to provide the "name" option.',
vnode.context
)
}
}
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
setScope(vnode)
/* istanbul ignore if */
if (__WEEX__) {
.....
} else {
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
if (process.env.NODE_ENV !== 'production' && data && data.pre) {
creatingElmInVPre--
}
} else if (isTrue(vnode.isComment)) {
vnode.elm = nodeOps.createComment(vnode.text)
insert(parentElm, vnode.elm, refElm)
} else {
vnode.elm = nodeOps.createTextNode(vnode.text)
insert(parentElm, vnode.elm, refElm)
}
}
// 自定义组件创建过程
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
// 查看实例是否已经存在
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
// 获取init钩子
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)
// 插入到dom
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
// 自定义组件创建过程
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
// 查看实例是否已经存在
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
// 获取init钩子
if (isDef(i = i.hook) && isDef(i = i.init)) {
// init()
// 组件实例化和挂载
i(vnode, false /* hydrating */)
}
if (isDef(vnode.componentInstance)) {
// 实例创建完毕,初始化它的属性
initComponent(vnode, insertedVnodeQueue)
// 插入到dom
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
- 查看实例是否已经存在
- 组件实例化和挂载
i(vnode, false /* hydrating */)
- 实例创建完毕,初始化它的属性
initComponent(vnode, insertedVnodeQueue)
- 插入到dom
insert(parentElm, vnode.elm, refElm)
接下来 组件内部有render函数,会创建watcher =》 执行_render() => _update() => _patch()