render.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import {
  2. warn,
  3. nextTick,
  4. emptyObject,
  5. handleError,
  6. defineReactive
  7. } from '../util/index'
  8. import { createElement } from '../vdom/create-element'
  9. import { installRenderHelpers } from './render-helpers/index'
  10. import { resolveSlots } from './render-helpers/resolve-slots'
  11. import { normalizeScopedSlots } from '../vdom/helpers/normalize-scoped-slots'
  12. import VNode, { createEmptyVNode } from '../vdom/vnode'
  13. import { isUpdatingChildComponent } from './lifecycle'
  14. import type { Component } from 'typescript/component'
  15. export function initRender(vm: Component) {
  16. vm._vnode = null // the root of the child tree
  17. vm._staticTrees = null // v-once cached trees
  18. const options = vm.$options
  19. const parentVnode = (vm.$vnode = options._parentVnode!) // the placeholder node in parent tree
  20. const renderContext = parentVnode && (parentVnode.context as Component)
  21. vm.$slots = resolveSlots(options._renderChildren, renderContext)
  22. vm.$scopedSlots = emptyObject
  23. // bind the createElement fn to this instance
  24. // so that we get proper render context inside it.
  25. // args order: tag, data, children, normalizationType, alwaysNormalize
  26. // internal version is used by render functions compiled from templates
  27. // @ts-expect-error
  28. vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  29. // normalization is always applied for the public version, used in
  30. // user-written render functions.
  31. // @ts-expect-error
  32. vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
  33. // $attrs & $listeners are exposed for easier HOC creation.
  34. // they need to be reactive so that HOCs using them are always updated
  35. const parentData = parentVnode && parentVnode.data
  36. /* istanbul ignore else */
  37. if (__DEV__) {
  38. defineReactive(
  39. vm,
  40. '$attrs',
  41. (parentData && parentData.attrs) || emptyObject,
  42. () => {
  43. !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
  44. },
  45. true
  46. )
  47. defineReactive(
  48. vm,
  49. '$listeners',
  50. options._parentListeners || emptyObject,
  51. () => {
  52. !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
  53. },
  54. true
  55. )
  56. } else {
  57. defineReactive(
  58. vm,
  59. '$attrs',
  60. (parentData && parentData.attrs) || emptyObject,
  61. null,
  62. true
  63. )
  64. defineReactive(
  65. vm,
  66. '$listeners',
  67. options._parentListeners || emptyObject,
  68. null,
  69. true
  70. )
  71. }
  72. }
  73. export let currentRenderingInstance: Component | null = null
  74. // for testing only
  75. export function setCurrentRenderingInstance(vm: Component) {
  76. currentRenderingInstance = vm
  77. }
  78. export function renderMixin(Vue: Component) {
  79. // install runtime convenience helpers
  80. installRenderHelpers(Vue.prototype)
  81. Vue.prototype.$nextTick = function (fn: Function) {
  82. return nextTick(fn, this)
  83. }
  84. Vue.prototype._render = function (): VNode {
  85. const vm: Component = this
  86. const { render, _parentVnode } = vm.$options
  87. if (_parentVnode) {
  88. vm.$scopedSlots = normalizeScopedSlots(
  89. _parentVnode.data!.scopedSlots,
  90. vm.$slots,
  91. vm.$scopedSlots
  92. )
  93. }
  94. // set parent vnode. this allows render functions to have access
  95. // to the data on the placeholder node.
  96. vm.$vnode = _parentVnode!
  97. // render self
  98. let vnode
  99. try {
  100. // There's no need to maintain a stack because all render fns are called
  101. // separately from one another. Nested component's render fns are called
  102. // when parent component is patched.
  103. currentRenderingInstance = vm
  104. vnode = render.call(vm._renderProxy, vm.$createElement)
  105. } catch (e: any) {
  106. handleError(e, vm, `render`)
  107. // return error render result,
  108. // or previous vnode to prevent render error causing blank component
  109. /* istanbul ignore else */
  110. if (__DEV__ && vm.$options.renderError) {
  111. try {
  112. vnode = vm.$options.renderError.call(
  113. vm._renderProxy,
  114. vm.$createElement,
  115. e
  116. )
  117. } catch (e: any) {
  118. handleError(e, vm, `renderError`)
  119. vnode = vm._vnode
  120. }
  121. } else {
  122. vnode = vm._vnode
  123. }
  124. } finally {
  125. currentRenderingInstance = null
  126. }
  127. // if the returned array contains only a single node, allow it
  128. if (Array.isArray(vnode) && vnode.length === 1) {
  129. vnode = vnode[0]
  130. }
  131. // return empty vnode in case the render function errored out
  132. if (!(vnode instanceof VNode)) {
  133. if (__DEV__ && Array.isArray(vnode)) {
  134. warn(
  135. 'Multiple root nodes returned from render function. Render function ' +
  136. 'should return a single root node.',
  137. vm
  138. )
  139. }
  140. vnode = createEmptyVNode()
  141. }
  142. // set parent
  143. vnode.parent = _parentVnode
  144. return vnode
  145. }
  146. }