coverPiccoverPic

render 函数如何运行1:createElement(v2)

前言

本文主要想看一下render是如何调用的,也就是renderVNode的过程。

流程总览

在 Vue 2 完整版中,实例化Vue后,调用(被重载的)$mount方法时就开始进行模板解析,整合了各种配置之后,调用baseCompile开始模板编译,生成renderstaticRenderFnsrender调用后生成虚拟 DOMVNodestaticRenderFns记录了静态节点的构造函数。

graph LR
A["(new Vue(...)).$mount(...)"] --> |call|B[compileToFunctions]
createCompilerCreator\nargs:baseCompile -->|return| createCompiler -->|return|B

B --> |"call & return"|baseCompile --> |return|C["{ render, staticRenderFns }"]

接着调用用于挂载节点的Vue.prototype.$mount方法,最后在_render中调用render函数

graph LR
A["(new Vue(...)).$mount(...)"] --> |"①"|B[compileToFunctions]
C --> D
A --> |"②"|D["options=this.$options\n options.render=render\n options.staticRenderFns=staticRenderFns"]

A-->|"③"|E[Vue.prototype.$mount] -->|call| F[mountComponent] -->|call|G[updateComponent]-->|call|H["_render"]-->|call|I["render"]

B --> |return|C["{ render, staticRenderFns }"]

函数入口

src/core/instance/render.ts,_render,略过作用域插槽的逻辑:

ts
  1. Vue.prototype._render = function (): VNode {
  2. // ...作用域插槽
  3. // set parent vnode. this allows render functions to have access
  4. // to the data on the placeholder node.
  5. vm.$vnode = _parentVnode!
  6. // render self
  7. let vnode
  8. try {
  9. setCurrentInstance(vm)
  10. currentRenderingInstance = vm
  11. vnode = render.call(vm._renderProxy, vm.$createElement)
  12. } catch (e: any) {
  13. handleError(e, vm, `render`)
  14. vnode = vm._vnode
  15. } finally {
  16. currentRenderingInstance = null
  17. setCurrentInstance()
  18. }
  19. if (isArray(vnode) && vnode.length === 1) {
  20. vnode = vnode[0]
  21. }
  22. if (!(vnode instanceof VNode)) {
  23. vnode = createEmptyVNode()
  24. }
  25. vnode.parent = _parentVnode
  26. return vnode
  27. }

render函数被调用,vm._renderProxyvm生产模式本身,开发模式下会附上错误输出的钩子,vm.$createElement用于生成VNode。如果使用render选项或者在setup中返回render的话,我们会这样子写:

ts
  1. render (h) {
  2. return h('div',{
  3.   // 给div绑定class属性
  4. class: {
  5. classTest: true,
  6. },
  7.   // 给div绑定样式
  8.   style: {
  9.   width: '200px',
  10. height: '200px',
  11.   }, 
  12.   // 给div绑定点击事件  
  13. on: {
  14. click: () => {
  15. console.log('点击事件')
  16. }
  17. },
  18. })
  19. }

类似于:

html
  1. <template>
  2. <div
  3. :class="{ classTest: true }"
  4. :style="{ width: '200px', height: '200px' }"
  5. @click="clickHandler"
  6. ></div>
  7. </template>
  8. <script>
  9. export default {
  10. methods: {
  11. clickHandler: () => {
  12. console.log('点击事件')
  13. }
  14. }
  15. }
  16. </script>

vm.$createElement就是上面的h函数。

ts
  1. // src/core/instance/render.ts
  2. vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

标准化输入

createElement先对输入的格式进行标准化:

ts
  1. // src/core/vdom/create-element.ts
  2. export function createElement(
  3. context: Component,
  4. tag: any,
  5. data: any,
  6. children: any,
  7. normalizationType: any,
  8. alwaysNormalize: boolean
  9. ): VNode | Array<VNode> {
  10. if (isArray(data) || isPrimitive(data)) {
  11. normalizationType = children
  12. children = data
  13. data = undefined
  14. }
  15. if (isTrue(alwaysNormalize)) {
  16. normalizationType = ALWAYS_NORMALIZE
  17. }
  18. return _createElement(context, tag, data, children, normalizationType)
  19. }
  20. export function _createElement(
  21. context: Component,
  22. tag?: string | Component | Function | Object,
  23. data?: VNodeData,
  24. children?: any,
  25. normalizationType?: number
  26. ): VNode | Array<VNode> {
  27. if (isDef(data) && isDef((data as any).__ob__)) {
  28. return createEmptyVNode()
  29. }
  30. // 处理 component 标签
  31. if (isDef(data) && isDef(data.is)) {
  32. tag = data.is
  33. }
  34. if (!tag) {
  35. // in case of component :is set to falsy value
  36. return createEmptyVNode()
  37. }
  38. // support single function children as default scoped slot
  39. // ... 对应在 h 函数传入插槽的情况
  40. if (isArray(children) && isFunction(children[0])) {
  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. // 模板生成的 render 函数一般调用这个
  49. children = simpleNormalizeChildren(children)
  50. }
  51. // ...
  52. }

下面来看看normalizeChildrensimpleNormalizeChildren两个函数,这两个函数做的事情很简单,下面代码就不用细看了:

ts
  1. // 模板生成的 render 函数一般调用这个
  2. export function simpleNormalizeChildren(children: any) {
  3. for (let i = 0; i < children.length; i++) {
  4. if (isArray(children[i])) {
  5. return Array.prototype.concat.apply([], children)
  6. }
  7. }
  8. return children
  9. }
  10. // <template>, <slot>, v-for 或者手写的 render, JSX 调用这个
  11. export function normalizeChildren(children: any): Array<VNode> | undefined {
  12. return isPrimitive(children)
  13. ? [createTextVNode(children)]
  14. : isArray(children)
  15. ? normalizeArrayChildren(children)
  16. : undefined
  17. }
  18. function isTextNode(node): boolean {
  19. return isDef(node) && isDef(node.text) && isFalse(node.isComment)
  20. }
  21. function normalizeArrayChildren(
  22. children: any,
  23. nestedIndex?: string
  24. ): Array<VNode> {
  25. const res: VNode[] = []
  26. let i, c, lastIndex, last
  27. for (i = 0; i < children.length; i++) {
  28. c = children[i]
  29. if (isUndef(c) || typeof c === 'boolean') continue
  30. lastIndex = res.length - 1
  31. last = res[lastIndex]
  32. // nested
  33. if (isArray(c)) {
  34. if (c.length > 0) {
  35. c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`)
  36. // merge adjacent text nodes
  37. if (isTextNode(c[0]) && isTextNode(last)) {
  38. res[lastIndex] = createTextVNode(last.text + c[0].text)
  39. c.shift()
  40. }
  41. res.push.apply(res, c)
  42. }
  43. } else if (isPrimitive(c)) {
  44. if (isTextNode(last)) {
  45. // merge adjacent text nodes
  46. // this is necessary for SSR hydration because text nodes are
  47. // essentially merged when rendered to HTML strings
  48. res[lastIndex] = createTextVNode(last.text + c)
  49. } else if (c !== '') {
  50. // convert primitive to vnode
  51. res.push(createTextVNode(c))
  52. }
  53. } else {
  54. if (isTextNode(c) && isTextNode(last)) {
  55. // merge adjacent text nodes
  56. res[lastIndex] = createTextVNode(last.text + c.text)
  57. } else {
  58. // default key for nested array children (likely generated by v-for)
  59. if (
  60. isTrue(children._isVList) &&
  61. isDef(c.tag) &&
  62. isUndef(c.key) &&
  63. isDef(nestedIndex)
  64. ) {
  65. c.key = `__vlist${nestedIndex}_${i}__`
  66. }
  67. res.push(c)
  68. }
  69. }
  70. }
  71. return res
  72. }

normalizeChildrensimpleNormalizeChildren都会把多层数组的子节点平铺。normalizeChildren此外还会创建文本类型的VNode,合并相邻文本,以及为没有key的子节点增加key

创建 VNode

继续看_createElement

ts
  1. export function _createElement(
  2. context: Component,
  3. tag?: string | Component | Function | Object,
  4. data?: VNodeData,
  5. children?: any,
  6. normalizationType?: number
  7. ): VNode | Array<VNode> {
  8. // ...
  9. let vnode, ns
  10. if (typeof tag === 'string') {
  11. let Ctor
  12. ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
  13. if (config.isReservedTag(tag)) {
  14. // 原生标签,div 之类的
  15. vnode = new VNode(
  16. config.parsePlatformTagName(tag),
  17. data,
  18. children,
  19. undefined,
  20. undefined,
  21. context
  22. )
  23. } else if (
  24. (!data || !data.pre) &&
  25. // 这里的 resolveAsset 会去组件实例的 components 中获得 tag (这时候是自定义组件名)对应的组件构造函数或者对象
  26. isDef((Ctor = resolveAsset(context.$options, 'components', tag)))
  27. ) {
  28. // 组件
  29. vnode = createComponent(Ctor, data, context, children, tag)
  30. } else {
  31. // 传入的标签不知道是什么
  32. vnode = new VNode(tag, data, children, undefined, undefined, context)
  33. }
  34. } else {
  35. // 给 h 函数传入组件的构造函数或者对象的情况
  36. vnode = createComponent(tag as any, data, context, children)
  37. }
  38. if (isArray(vnode)) {
  39. return vnode
  40. } else if (isDef(vnode)) {
  41. if (isDef(ns)) applyNS(vnode, ns)
  42. if (isDef(data)) registerDeepBindings(data)
  43. return vnode
  44. } else {
  45. return createEmptyVNode()
  46. }
  47. }

我们不难猜出createComponent是处理自定义组件的,new VNode创建普通的标签。

先看VNodenew VNode(...)创建出一个空壳 VNode,这也展示了 Vue 的虚拟 DOM 的数据结构:

ts
  1. export default class VNode {
  2. //...
  3. constructor(
  4. tag?: string,
  5. data?: VNodeData,
  6. children?: Array<VNode> | null,
  7. text?: string,
  8. elm?: Node,
  9. context?: Component,
  10. componentOptions?: VNodeComponentOptions,
  11. asyncFactory?: Function
  12. ) {
  13. this.tag = tag
  14. this.data = data
  15. this.children = children
  16. this.text = text
  17. this.elm = elm
  18. this.ns = undefined
  19. this.context = context
  20. this.fnContext = undefined
  21. this.fnOptions = undefined
  22. this.fnScopeId = undefined
  23. this.key = data && data.key
  24. this.componentOptions = componentOptions
  25. this.componentInstance = undefined
  26. this.parent = undefined
  27. this.raw = false
  28. this.isStatic = false
  29. this.isRootInsert = true
  30. this.isComment = false
  31. this.isCloned = false
  32. this.isOnce = false
  33. this.asyncFactory = asyncFactory
  34. this.asyncMeta = undefined
  35. this.isAsyncPlaceholder = false
  36. }
  37. }

然后createComponent

ts
  1. export function createComponent(
  2. Ctor: typeof Component | Function | ComponentOptions | void,
  3. data: VNodeData | undefined,
  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. if (isObject(Ctor)) {
  13. Ctor = baseCtor.extend(Ctor as typeof Component)
  14. }
  15. if (typeof Ctor !== 'function') {
  16. return
  17. }
  18. // ... 异步组件
  19. data = data || {}
  20. // ... 合并继承而来的属性
  21. // ... v-model
  22. // 获取组件的 props
  23. const propsData = extractPropsFromVNodeData(data, Ctor, tag)
  24. // ... functional 组件,就是一种用于没有实例和状态的组件,选项加上 functional: true 即可,
  25. // 可用于增强组件功能或者作为性能更好的纯展示组件
  26. // extract listeners, since these needs to be treated as
  27. // child component listeners instead of DOM listeners
  28. const listeners = data.on
  29. // replace with listeners with .native modifier
  30. // so it gets processed during parent component patch.
  31. data.on = data.nativeOn
  32. // ... 抽象组件,就是一种没有生命周期的组件,选项加上 abstract: true 即可,
  33. // 可用于增强组件功能
  34. // 创建 init(渲染子组件), destory(发起销毁生命周期), insert(发起挂载生命周期) 等钩子
  35. installComponentHooks(data)
  36. // 最后创建 VNode
  37. // @ts-expect-error
  38. const name = getComponentName(Ctor.options) || tag
  39. const vnode = new VNode(
  40. // @ts-expect-error
  41. `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
  42. data,
  43. undefined,
  44. undefined,
  45. undefined,
  46. context,
  47. // @ts-expect-error
  48. { Ctor, propsData, listeners, tag, children },
  49. asyncFactory
  50. )
  51. return vnode
  52. }

感觉这里只是对组件做了初始化的处理,组件的具体逻辑就不展开了…

0 条评论未登录用户
Ctrl or + Enter 评论
© 2023-2025 LittleRangiferTarandus, All rights reserved.
🌸 Run