Vue.js使用createElement方法创建VNode
定义在 src/core/vdom/create-element.js 中

  1. // wrapper function for providing a more flexible interface
  2. // without getting yelled at by flow
  3. export function createElement (
  4. context: Component,
  5. tag: any,
  6. data: any,
  7. children: any,
  8. normalizationType: any,
  9. alwaysNormalize: boolean
  10. ): VNode | Array<VNode> {
  11. if (Array.isArray(data) || isPrimitive(data)) {
  12. normalizationType = children
  13. children = data
  14. data = undefined
  15. }
  16. if (isTrue(alwaysNormalize)) {
  17. normalizationType = ALWAYS_NORMALIZE
  18. }
  19. return _createElement(context, tag, data, children, normalizationType)
  20. }

createElement方法实际上是对_createElement方法的封装,它允许传入的参数更加灵活,在处理这些参数之后调用真正创建VNode的函数_createElement

  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. vnode = createComponent(tag, data, context, children)
  81. }
  82. if (Array.isArray(vnode)) {
  83. return vnode
  84. } else if (isDef(vnode)) {
  85. if (isDef(ns)) applyNS(vnode, ns)
  86. if (isDef(data)) registerDeepBindings(data)
  87. return vnode
  88. } else {
  89. return createEmptyVNode()
  90. }
  91. }

children 的规范化

由于Virtual DOM实际上是一个树状结构,每一个VNode可能会有若干个子节点,这些子节点应该也是VNode类型
_createElement接收的第4个参数children是任意类型的,因此需要把它们规范成VNode类型
根据normalizationType的不同,调用了normalizeChildren(children)和simpleNormalizeChildren(children)方法
定义在 src/core/vdom/helpers/normalzie-children.js 中

  1. // The template compiler attempts to minimize the need for normalization by
  2. // statically analyzing the template at compile time.
  3. //
  4. // For plain HTML markup, normalization can be completely skipped because the
  5. // generated render function is guaranteed to return Array<VNode>. There are
  6. // two cases where extra normalization is needed:
  7. // 1. When the children contains components - because a functional component
  8. // may return an Array instead of a single root. In this case, just a simple
  9. // normalization is needed - if any child is an Array, we flatten the whole
  10. // thing with Array.prototype.concat. It is guaranteed to be only 1-level deep
  11. // because functional components already normalize their own children.
  12. // simpleNormalizeChildren方法调用场景是 render函数是编译生成的
  13. // 理论上编译生成的children都已经是VNode类型的
  14. // 但是functional component函数式组件返回的是一个数组而不是一个根节点,所以会通过Array.prototype.concat方法把整个children数组打平,让它的深度只有一层
  15. export function simpleNormalizeChildren (children: any) {
  16. for (let i = 0; i < children.length; i++) {
  17. if (Array.isArray(children[i])) {
  18. return Array.prototype.concat.apply([], children)
  19. }
  20. }
  21. return children
  22. }
  23. // 2. When the children contains constructs that always generated nested Arrays,
  24. // e.g. <template>, <slot>, v-for, or when the children is provided by user
  25. // with hand-written render functions / JSX. In such cases a full normalization
  26. // is needed to cater to all possible types of children values.
  27. // normalizeChildren方法调用场景:
  28. // 一个是render函数是用户手写的,当children只有一个节点时Vue.js从接口层面允许用户把children写出基础类型用来创建单个简单的文本节点,这种情况会调用createTextVNode创建一个文本节点的VNode
  29. // 另一个是当编译slot、v-for的时候会产生嵌套数组的情况,会调用normalizeArrayChildren方法
  30. export function normalizeChildren (children: any): ?Array<VNode> {
  31. return isPrimitive(children)
  32. ? [createTextVNode(children)]
  33. : Array.isArray(children)
  34. ? normalizeArrayChildren(children)
  35. : undefined
  36. }

normalizeArrayChildren

  1. function normalizeArrayChildren (
  2. children: any, // 要规范的子节点
  3. nestedIndex?: string // 嵌套的索引
  4. ): Array<VNode> {
  5. const res = []
  6. let i, c, lastIndex, last
  7. // 遍历所有的children
  8. for (i = 0; i < children.length; i++) {
  9. // 单个节点
  10. c = children[i]
  11. // undefined || boolean类型直接退出此次循环
  12. if (isUndef(c) || typeof c === 'boolean') continue
  13. lastIndex = res.length - 1
  14. last = res[lastIndex]
  15. // nested
  16. // 数组类型
  17. if (Array.isArray(c)) {
  18. if (c.length > 0) {
  19. // 递归调用normalizeArrayChildren
  20. c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`)
  21. // merge adjacent text nodes
  22. if (isTextNode(c[0]) && isTextNode(last)) {
  23. res[lastIndex] = createTextVNode(last.text + (c[0]: any).text)
  24. c.shift()
  25. }
  26. res.push.apply(res, c)
  27. }
  28. } else if (isPrimitive(c)) { // 如果是基础类型
  29. if (isTextNode(last)) {
  30. // merge adjacent text nodes
  31. // this is necessary for SSR hydration because text nodes are
  32. // essentially merged when rendered to HTML strings
  33. // 通过createTextVNode方法转换成VNode类型
  34. res[lastIndex] = createTextVNode(last.text + c)
  35. } else if (c !== '') {
  36. // convert primitive to vnode
  37. res.push(createTextVNode(c))
  38. }
  39. } else {
  40. // 如果存在两个连续的text节点会把它们合并成一个text节点
  41. if (isTextNode(c) && isTextNode(last)) {
  42. // merge adjacent text nodes
  43. res[lastIndex] = createTextVNode(last.text + c.text)
  44. } else {
  45. // default key for nested array children (likely generated by v-for)
  46. // 如果children是一个列表并且列表还存在嵌套情况,则根据nestedIndex去更新它的key
  47. if (isTrue(children._isVList) &&
  48. isDef(c.tag) &&
  49. isUndef(c.key) &&
  50. isDef(nestedIndex)) {
  51. c.key = `__vlist${nestedIndex}_${i}__`
  52. }
  53. res.push(c)
  54. }
  55. }
  56. }
  57. return res
  58. }

children表示要规范的子节点,nestedIndex表示嵌套的索引,因为单个child可能是一个数组类型
经过对children的规范化,children变成了一个类型为VNode的Array

VNode 的创建

在_createElement函数中规范化children后回去创建一个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. // 创建VNode
  51. let vnode, ns
  52. // 如果tag是字符串类型
  53. if (typeof tag === 'string') {
  54. let Ctor
  55. ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
  56. // 如果是内置节点
  57. if (config.isReservedTag(tag)) {
  58. // platform built-in elements
  59. if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn) && data.tag !== 'component') {
  60. warn(
  61. `The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
  62. context
  63. )
  64. }
  65. // 直接创建一个普通VNode
  66. vnode = new VNode(
  67. config.parsePlatformTagName(tag), data, children,
  68. undefined, undefined, context
  69. )
  70. } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { // 如果是已注册的组件名
  71. // component
  72. // 通过createComponent创建一个组件类型的VNode
  73. vnode = createComponent(Ctor, data, context, children, tag)
  74. } else { // 如果不是已注册的组件名
  75. // unknown or unlisted namespaced elements
  76. // check at runtime because it may get assigned a namespace when its
  77. // parent normalizes children
  78. // 创建一个未知的标签的VNode
  79. vnode = new VNode(
  80. tag, data, children,
  81. undefined, undefined, context
  82. )
  83. }
  84. } else { // 如果tag是component类型
  85. // direct component options / constructor
  86. // 直接调用createComponent创建一个组件类型的VNode节点
  87. vnode = createComponent(tag, data, context, children)
  88. }
  89. if (Array.isArray(vnode)) {
  90. return vnode
  91. } else if (isDef(vnode)) {
  92. if (isDef(ns)) applyNS(vnode, ns)
  93. if (isDef(data)) registerDeepBindings(data)
  94. return vnode
  95. } else {
  96. return createEmptyVNode()
  97. }
  98. }