h()

看到这个函数你可能会有些许困惑,为什么叫h呢?代表着什么呢?

h 其实代表的是 hyperscript 。它是 HTML 的一部分,表示的是超文本标记语言,当我们正在处理一个脚本的时候,在虚拟 DOM 节点中去使用它进行替换已成为一种惯例。这个定义同时也被运用到其他的框架文档中

Hyperscript 它本身表示的是 “生成描述 HTML 结构的脚本”

好了,了解了什么是 h,现在我们来看官方对他的一个定义

定义: 返回一个“虚拟节点” ,通常缩写为 VNode: 一个普通对象,其中包含向 Vue 描述它应该在页面上呈现哪种节点的信息,包括对任何子节点的描述。用于手动编写render

语法

  1. // type only
  2. h('div')
  3. // type + props
  4. h('div', {})
  5. // type + omit props + children
  6. // Omit props does NOT support named slots
  7. h('div', []) // array
  8. h('div', 'foo') // text
  9. h('div', h('br')) // vnode
  10. h(Component, () => {}) // default slot
  11. // type + props + children
  12. h('div', {}, []) // array
  13. h('div', {}, 'foo') // text
  14. h('div', {}, h('br')) // vnode
  15. h(Component, {}, () => {}) // default slot
  16. h(Component, {}, {}) // named slots
  17. // named slots without props requires explicit `null` to avoid ambiguity
  18. h(Component, null, {})

举个栗子

  1. const App = {
  2. render() {
  3. return Vue.h('h1', {}, 'Hello Vue3js.cn')
  4. }
  5. }
  6. Vue.createApp(App).mount('#app')

亲自试一试

都干了些啥

h 接收三个参数

  • type 元素的类型
  • propsOrChildren 数据对象, 这里主要表示(props, attrs, dom props, class 和 style)
  • children 子节点
    1. export function h(type: any, propsOrChildren?: any, children?: any): VNode {
    2. if (arguments.length === 2) {
    3. if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
    4. // single vnode without props
    5. if (isVNode(propsOrChildren)) {
    6. return createVNode(type, null, [propsOrChildren])
    7. }
    8. // props without children
    9. return createVNode(type, propsOrChildren)
    10. } else {
    11. // omit props
    12. return createVNode(type, null, propsOrChildren)
    13. }
    14. } else {
    15. if (isVNode(children)) {
    16. children = [children]
    17. }
    18. return createVNode(type, propsOrChildren, children)
    19. }
    20. }
    _createVNode 做的事情也很简单
  1. function _createVNode(
  2. type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  3. props: (Data & VNodeProps) | null = null,
  4. children: unknown = null,
  5. // 更新标志
  6. patchFlag: number = 0,
  7. // 自定义属性
  8. dynamicProps: string[] | null = null,
  9. // 是否是动态节点,(v-if v-for)
  10. isBlockNode = false
  11. ): VNode {
  12. // type必传参数
  13. if (!type || type === NULL_DYNAMIC_COMPONENT) {
  14. if (__DEV__ && !type) {
  15. warn(`Invalid vnode type when creating vnode: ${type}.`)
  16. }
  17. type = Comment
  18. }
  19. // Class 类型的type标准化
  20. // class component normalization.
  21. if (isFunction(type) && '__vccOpts' in type) {
  22. type = type.__vccOpts
  23. }
  24. // class & style normalization.
  25. if (props) {
  26. // props 如果是响应式,clone 一个副本
  27. if (isProxy(props) || InternalObjectKey in props) {
  28. props = extend({}, props)
  29. }
  30. let { class: klass, style } = props
  31. // 标准化class, 支持 string , array, object 三种形式
  32. if (klass && !isString(klass)) {
  33. props.class = normalizeClass(klass)
  34. }
  35. // 标准化style, 支持 array ,object 两种形式
  36. if (isObject(style)) {
  37. // reactive state objects need to be cloned since they are likely to be
  38. // mutated
  39. if (isProxy(style) && !isArray(style)) {
  40. style = extend({}, style)
  41. }
  42. props.style = normalizeStyle(style)
  43. }
  44. }
  45. // encode the vnode type information into a bitmap
  46. const shapeFlag = isString(type)
  47. ? ShapeFlags.ELEMENT
  48. : __FEATURE_SUSPENSE__ && isSuspense(type)
  49. ? ShapeFlags.SUSPENSE
  50. : isTeleport(type)
  51. ? ShapeFlags.TELEPORT
  52. : isObject(type)
  53. ? ShapeFlags.STATEFUL_COMPONENT
  54. : isFunction(type)
  55. ? ShapeFlags.FUNCTIONAL_COMPONENT
  56. : 0
  57. if (__DEV__ && shapeFlag & ShapeFlags.STATEFUL_COMPONENT && isProxy(type)) {
  58. type = toRaw(type)
  59. warn(
  60. `Vue received a Component which was made a reactive object. This can ` +
  61. `lead to unnecessary performance overhead, and should be avoided by ` +
  62. `marking the component with \`markRaw\` or using \`shallowRef\` ` +
  63. `instead of \`ref\`.`,
  64. `\nComponent that was made reactive: `,
  65. type
  66. )
  67. }
  68. // 构造 VNode 模型
  69. const vnode: VNode = {
  70. __v_isVNode: true,
  71. __v_skip: true,
  72. type,
  73. props,
  74. key: props && normalizeKey(props),
  75. ref: props && normalizeRef(props),
  76. scopeId: currentScopeId,
  77. children: null,
  78. component: null,
  79. suspense: null,
  80. dirs: null,
  81. transition: null,
  82. el: null,
  83. anchor: null,
  84. target: null,
  85. targetAnchor: null,
  86. staticCount: 0,
  87. shapeFlag,
  88. patchFlag,
  89. dynamicProps,
  90. dynamicChildren: null,
  91. appContext: null
  92. }
  93. normalizeChildren(vnode, children)
  94. // presence of a patch flag indicates this node needs patching on updates.
  95. // component nodes also should always be patched, because even if the
  96. // component doesn't need to update, it needs to persist the instance on to
  97. // the next vnode so that it can be properly unmounted later.
  98. // patchFlag 标志存在表示节点需要更新,组件节点一直存在 patchFlag,因为即使不需要更新,它需要将实例持久化到下一个 vnode,以便以后可以正确卸载它
  99. if (
  100. shouldTrack > 0 &&
  101. !isBlockNode &&
  102. currentBlock &&
  103. // the EVENTS flag is only for hydration and if it is the only flag, the
  104. // vnode should not be considered dynamic due to handler caching.
  105. patchFlag !== PatchFlags.HYDRATE_EVENTS &&
  106. (patchFlag > 0 ||
  107. shapeFlag & ShapeFlags.SUSPENSE ||
  108. shapeFlag & ShapeFlags.TELEPORT ||
  109. shapeFlag & ShapeFlags.STATEFUL_COMPONENT ||
  110. shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT)
  111. ) {
  112. // 压入 VNode 栈
  113. currentBlock.push(vnode)
  114. }
  115. return vnode
  116. }

总结

到这里,h 函数已经全部看完了,我们现在知道 h 叫法的由来,其函数内部逻辑只做参数检查,真正的主角是 _createVNode

_createVNode 做的事情有

  1. 标准化 props class
  2. VNode 打上编码标记
  3. 创建 VNode
  4. 标准化子节点

有的同学可能会有疑问🤔️,VNode 最后是怎么转换成真实的 DOM 呢?

这边有单独拉出来讲,请点VNode