_createElement方法中有对参数tag的判断,如果是一个普通的html标签,则会实例化一个普通VNode节点,否则通过createComponent方法创建一个组件VNode

  1. export function _createElement (
  2. context: Component, // 上下文环境
  3. tag?: string | Class<Component> | Function | Object, // 标签
  4. data?: VNodeData, // VNode数据
  5. children?: any, // 当前VNode的子节点 需规范为标准的VNode数组
  6. normalizationType?: number // 子节点规范的类型 参考render函数是编译生成还是用户手写
  7. ): VNode | Array<VNode> {
  8. if (isDef(data) && isDef((data: any).__ob__)) {
  9. // 为了不影响看代码 先注释了
  10. // process.env.NODE_ENV !== 'production' && warn(
  11. // `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
  12. // 'Always create fresh vnode data objects in each render!',
  13. // context
  14. // )
  15. return createEmptyVNode()
  16. }
  17. // object syntax in v-bind
  18. if (isDef(data) && isDef(data.is)) {
  19. tag = data.is
  20. }
  21. if (!tag) {
  22. // in case of component :is set to falsy value
  23. return createEmptyVNode()
  24. }
  25. // warn against non-primitive key
  26. if (process.env.NODE_ENV !== 'production' &&
  27. isDef(data) && isDef(data.key) && !isPrimitive(data.key)
  28. ) {
  29. if (!__WEEX__ || !('@binding' in data.key)) {
  30. warn(
  31. 'Avoid using non-primitive value as key, ' +
  32. 'use string/number value instead.',
  33. context
  34. )
  35. }
  36. }
  37. // support single function children as default scoped slot
  38. if (Array.isArray(children) &&
  39. typeof children[0] === 'function'
  40. ) {
  41. data = data || {}
  42. data.scopedSlots = { default: children[0] }
  43. children.length = 0
  44. }
  45. if (normalizationType === ALWAYS_NORMALIZE) {
  46. children = normalizeChildren(children)
  47. } else if (normalizationType === SIMPLE_NORMALIZE) {
  48. children = simpleNormalizeChildren(children)
  49. }
  50. let vnode, ns
  51. if (typeof tag === 'string') {
  52. let Ctor
  53. ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
  54. if (config.isReservedTag(tag)) {
  55. // platform built-in elements
  56. if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn) && data.tag !== 'component') {
  57. warn(
  58. `The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
  59. context
  60. )
  61. }
  62. vnode = new VNode(
  63. config.parsePlatformTagName(tag), data, children,
  64. undefined, undefined, context
  65. )
  66. } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
  67. // component
  68. vnode = createComponent(Ctor, data, context, children, tag)
  69. } else {
  70. // unknown or unlisted namespaced elements
  71. // check at runtime because it may get assigned a namespace when its
  72. // parent normalizes children
  73. vnode = new VNode(
  74. tag, data, children,
  75. undefined, undefined, context
  76. )
  77. }
  78. } else {
  79. // direct component options / constructor
  80. // 本质上是一个component类型,会直接通过createComponent方法创建vnode
  81. vnode = createComponent(tag, data, context, children)
  82. }
  83. if (Array.isArray(vnode)) {
  84. return vnode
  85. } else if (isDef(vnode)) {
  86. if (isDef(ns)) applyNS(vnode, ns)
  87. if (isDef(data)) registerDeepBindings(data)
  88. return vnode
  89. } else {
  90. return createEmptyVNode()
  91. }
  92. }

createComponent

定义在 src/core/vdom/create-component.js 文件中

  1. export function createComponent (
  2. Ctor: Class<Component> | Function | Object | void,
  3. data: ?VNodeData,
  4. context: Component,
  5. children: ?Array<VNode>,
  6. tag?: string
  7. ): VNode | Array<VNode> | void {
  8. if (isUndef(Ctor)) {
  9. return
  10. }
  11. const baseCtor = context.$options._base
  12. // plain options object: turn it into a constructor
  13. if (isObject(Ctor)) {
  14. Ctor = baseCtor.extend(Ctor)
  15. }
  16. // if at this stage it's not a constructor or an async component factory,
  17. // reject.
  18. if (typeof Ctor !== 'function') {
  19. if (process.env.NODE_ENV !== 'production') {
  20. warn(`Invalid Component definition: ${String(Ctor)}`, context)
  21. }
  22. return
  23. }
  24. // async component
  25. let asyncFactory
  26. if (isUndef(Ctor.cid)) {
  27. asyncFactory = Ctor
  28. Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
  29. if (Ctor === undefined) {
  30. // return a placeholder node for async component, which is rendered
  31. // as a comment node but preserves all the raw information for the node.
  32. // the information will be used for async server-rendering and hydration.
  33. return createAsyncPlaceholder(
  34. asyncFactory,
  35. data,
  36. context,
  37. children,
  38. tag
  39. )
  40. }
  41. }
  42. data = data || {}
  43. // resolve constructor options in case global mixins are applied after
  44. // component constructor creation
  45. resolveConstructorOptions(Ctor)
  46. // transform component v-model data into props & events
  47. if (isDef(data.model)) {
  48. transformModel(Ctor.options, data)
  49. }
  50. // extract props
  51. const propsData = extractPropsFromVNodeData(data, Ctor, tag)
  52. // functional component
  53. if (isTrue(Ctor.options.functional)) {
  54. return createFunctionalComponent(Ctor, propsData, data, context, children)
  55. }
  56. // extract listeners, since these needs to be treated as
  57. // child component listeners instead of DOM listeners
  58. const listeners = data.on
  59. // replace with listeners with .native modifier
  60. // so it gets processed during parent component patch.
  61. data.on = data.nativeOn
  62. if (isTrue(Ctor.options.abstract)) {
  63. // abstract components do not keep anything
  64. // other than props & listeners & slot
  65. // work around flow
  66. const slot = data.slot
  67. data = {}
  68. if (slot) {
  69. data.slot = slot
  70. }
  71. }
  72. // install component management hooks onto the placeholder node
  73. installComponentHooks(data)
  74. // return a placeholder vnode
  75. const name = Ctor.options.name || tag
  76. const vnode = new VNode(
  77. `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
  78. data, undefined, undefined, undefined, context,
  79. { Ctor, propsData, listeners, tag, children },
  80. asyncFactory
  81. )
  82. // Weex specific: invoke recycle-list optimized @render function for
  83. // extracting cell-slot template.
  84. // https://github.com/Hanks10100/weex-native-directive/tree/master/component
  85. /* istanbul ignore if */
  86. if (__WEEX__ && isRecyclableComponent(vnode)) {
  87. return renderRecyclableComponentTemplate(vnode)
  88. }
  89. return vnode
  90. }

createComponent实现流程

构造子类构造函数

  1. const baseCtor = context.$options._base
  2. // plain options object: turn it into a constructor
  3. if (isObject(Ctor)) {
  4. Ctor = baseCtor.extend(Ctor)
  5. }

在编写一个组件时通常都是创建一个普通对象

  1. import HelloWorld from './components/HelloWorld'
  2. export default {
  3. name: 'app',
  4. components: {
  5. HelloWorld
  6. }
  7. }

这个例子中export的是一个对象,createComponent代码逻辑会执行到Ctor = baseCtor.extend(Ctor)
baseCtor实际上就是Vue,这个定义在最开始初始化Vue的阶段
在src/core/global-api/index.js 中initGlobalAPI 方法中有这样一段逻辑

  1. // this is used to identify the "base" constructor to extend all plain-object
  2. // components with in Weex's multi-instance scenarios.
  3. Vue.options._base = Vue

这里定义的是Vue.options
context.$options实际上在src/core/instance/init.js 里 Vue 原型上的 _init 函数中有这么一段逻辑

  1. // 把Vue构造函数的options和用户传入的options做一层合并,到vm.$options上
  2. vm.$options = mergeOptions(
  3. resolveConstructorOptions(vm.constructor),
  4. options || {},
  5. vm
  6. )

把Vue上的一些option扩展到了vm.$options上,所以通过vm.$options._base就能拿到Vue这个构造函数了

Vue.extend函数

作用是构造一个Vue的子类
定义在 src/core/global-api/extend.js 中

  1. Vue.extend = function (extendOptions: Object): Function {
  2. extendOptions = extendOptions || {}
  3. const Super = this // Vue
  4. const SuperId = Super.cid
  5. const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
  6. if (cachedCtors[SuperId]) {
  7. return cachedCtors[SuperId]
  8. }
  9. const name = extendOptions.name || Super.options.name
  10. if (process.env.NODE_ENV !== 'production' && name) {
  11. validateComponentName(name)
  12. }
  13. // 实例化Sub时就会执行this._init再次走到Vue实例的初始化逻辑
  14. const Sub = function VueComponent (options) {
  15. this._init(options)
  16. }
  17. // 原型继承的方式把一个纯对象转换一个继承于Vue的构造器Sub并返回
  18. Sub.prototype = Object.create(Super.prototype)
  19. Sub.prototype.constructor = Sub
  20. Sub.cid = cid++
  21. // 对Sub这个对象本身扩展了一些属性
  22. // 扩展options
  23. Sub.options = mergeOptions(
  24. Super.options,
  25. extendOptions
  26. )
  27. Sub['super'] = Super
  28. // For props and computed properties, we define the proxy getters on
  29. // the Vue instances at extension time, on the extended prototype. This
  30. // avoids Object.defineProperty calls for each instance created.
  31. // 对配置中的props和computed做初始化
  32. if (Sub.options.props) {
  33. initProps(Sub)
  34. }
  35. if (Sub.options.computed) {
  36. initComputed(Sub)
  37. }
  38. // allow further extension/mixin/plugin usage
  39. // 添加全局API
  40. Sub.extend = Super.extend
  41. Sub.mixin = Super.mixin
  42. Sub.use = Super.use
  43. // create asset registers, so extended classes
  44. // can have their private assets too.
  45. ASSET_TYPES.forEach(function (type) {
  46. Sub[type] = Super[type]
  47. })
  48. // enable recursive self-lookup
  49. if (name) {
  50. Sub.options.components[name] = Sub
  51. }
  52. // keep a reference to the super options at extension time.
  53. // later at instantiation we can check if Super's options have
  54. // been updated.
  55. Sub.superOptions = Super.options
  56. Sub.extendOptions = extendOptions
  57. Sub.sealedOptions = extend({}, Sub.options)
  58. // cache constructor
  59. // 对Sub构造函数做了缓存,避免多次执行Vue.extend时对同一个子组件重复构造
  60. cachedCtors[SuperId] = Sub
  61. return Sub
  62. }

安装组件钩子函数

  1. // install component management hooks onto the placeholder node
  2. installComponentHooks(data)

在初始化一个Component类型的VNode的过程中实现了几个钩子函数

  1. // inline hooks to be invoked on component VNodes during patch
  2. const componentVNodeHooks = {
  3. init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
  4. if (
  5. vnode.componentInstance &&
  6. !vnode.componentInstance._isDestroyed &&
  7. vnode.data.keepAlive
  8. ) {
  9. // kept-alive components, treat as a patch
  10. const mountedNode: any = vnode // work around flow
  11. componentVNodeHooks.prepatch(mountedNode, mountedNode)
  12. } else {
  13. const child = vnode.componentInstance = createComponentInstanceForVnode(
  14. vnode,
  15. activeInstance
  16. )
  17. child.$mount(hydrating ? vnode.elm : undefined, hydrating)
  18. }
  19. },
  20. prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
  21. const options = vnode.componentOptions
  22. const child = vnode.componentInstance = oldVnode.componentInstance
  23. updateChildComponent(
  24. child,
  25. options.propsData, // updated props
  26. options.listeners, // updated listeners
  27. vnode, // new parent vnode
  28. options.children // new children
  29. )
  30. },
  31. insert (vnode: MountedComponentVNode) {
  32. const { context, componentInstance } = vnode
  33. if (!componentInstance._isMounted) {
  34. componentInstance._isMounted = true
  35. callHook(componentInstance, 'mounted')
  36. }
  37. if (vnode.data.keepAlive) {
  38. if (context._isMounted) {
  39. // vue-router#1212
  40. // During updates, a kept-alive component's child components may
  41. // change, so directly walking the tree here may call activated hooks
  42. // on incorrect children. Instead we push them into a queue which will
  43. // be processed after the whole patch process ended.
  44. queueActivatedComponent(componentInstance)
  45. } else {
  46. activateChildComponent(componentInstance, true /* direct */)
  47. }
  48. }
  49. },
  50. destroy (vnode: MountedComponentVNode) {
  51. const { componentInstance } = vnode
  52. if (!componentInstance._isDestroyed) {
  53. if (!vnode.data.keepAlive) {
  54. componentInstance.$destroy()
  55. } else {
  56. deactivateChildComponent(componentInstance, true /* direct */)
  57. }
  58. }
  59. }
  60. }
  61. const hooksToMerge = Object.keys(componentVNodeHooks)
  62. // 把componentVNodeHooks的钩子函数合并到data.hook中,在VNode执行patch的过程中执行相关的钩子函数
  63. function installComponentHooks (data: VNodeData) {
  64. const hooks = data.hook || (data.hook = {})
  65. for (let i = 0; i < hooksToMerge.length; i++) {
  66. const key = hooksToMerge[i]
  67. const existing = hooks[key]
  68. const toMerge = componentVNodeHooks[key]
  69. // 合并策略
  70. // 如果某个时机的钩子已经存在data.hook中,就通过执行mergeHook函数做合并
  71. if (existing !== toMerge && !(existing && existing._merged)) {
  72. hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
  73. }
  74. }
  75. }
  76. function mergeHook (f1: any, f2: any): Function {
  77. // 做合并-在最终执行时依次执行这两个钩子函数
  78. const merged = (a, b) => {
  79. // flow complains about extra args which is why we use any
  80. f1(a, b)
  81. f2(a, b)
  82. }
  83. merged._merged = true
  84. return merged
  85. }

实例化 VNode

  1. // return a placeholder vnode
  2. const name = Ctor.options.name || tag
  3. // 实例化一个VNode
  4. const vnode = new VNode(
  5. `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
  6. data, undefined, undefined, undefined, context,
  7. { Ctor, propsData, listeners, tag, children },
  8. asyncFactory
  9. )
  10. // ...
  11. // 返回实例化的VNode
  12. return vnode

和普通元素节点的vnode不同的是,组件的vnode是没有children的