componentRenderUtils.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. import {
  2. ComponentInternalInstance,
  3. FunctionalComponent,
  4. Data
  5. } from './component'
  6. import {
  7. VNode,
  8. normalizeVNode,
  9. createVNode,
  10. Comment,
  11. cloneVNode
  12. } from './vnode'
  13. import { handleError, ErrorCodes } from './errorHandling'
  14. import { PatchFlags, ShapeFlags, EMPTY_OBJ, isOn } from '@vue/shared'
  15. import { warn } from './warning'
  16. // mark the current rendering instance for asset resolution (e.g.
  17. // resolveComponent, resolveDirective) during render
  18. export let currentRenderingInstance: ComponentInternalInstance | null = null
  19. export function setCurrentRenderingInstance(
  20. instance: ComponentInternalInstance | null
  21. ) {
  22. currentRenderingInstance = instance
  23. }
  24. // dev only flag to track whether $attrs was used during render.
  25. // If $attrs was used during render then the warning for failed attrs
  26. // fallthrough can be suppressed.
  27. let accessedAttrs: boolean = false
  28. export function markAttrsAccessed() {
  29. accessedAttrs = true
  30. }
  31. export function renderComponentRoot(
  32. instance: ComponentInternalInstance
  33. ): VNode {
  34. const {
  35. type: Component,
  36. parent,
  37. vnode,
  38. proxy,
  39. withProxy,
  40. props,
  41. slots,
  42. attrs,
  43. vnodeHooks,
  44. emit,
  45. renderCache
  46. } = instance
  47. let result
  48. currentRenderingInstance = instance
  49. if (__DEV__) {
  50. accessedAttrs = false
  51. }
  52. try {
  53. if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
  54. // withProxy is a proxy with a different `has` trap only for
  55. // runtime-compiled render functions using `with` block.
  56. const proxyToUse = withProxy || proxy
  57. result = normalizeVNode(
  58. instance.render!.call(proxyToUse, proxyToUse, renderCache)
  59. )
  60. } else {
  61. // functional
  62. const render = Component as FunctionalComponent
  63. result = normalizeVNode(
  64. render.length > 1
  65. ? render(props, {
  66. attrs,
  67. slots,
  68. emit
  69. })
  70. : render(props, null as any /* we know it doesn't need it */)
  71. )
  72. }
  73. // attr merging
  74. let fallthroughAttrs
  75. if (
  76. Component.inheritAttrs !== false &&
  77. attrs !== EMPTY_OBJ &&
  78. (fallthroughAttrs = getFallthroughAttrs(attrs))
  79. ) {
  80. if (
  81. result.shapeFlag & ShapeFlags.ELEMENT ||
  82. result.shapeFlag & ShapeFlags.COMPONENT
  83. ) {
  84. result = cloneVNode(result, fallthroughAttrs)
  85. // If the child root node is a compiler optimized vnode, make sure it
  86. // force update full props to account for the merged attrs.
  87. if (result.dynamicChildren !== null) {
  88. result.patchFlag |= PatchFlags.FULL_PROPS
  89. }
  90. } else if (__DEV__ && !accessedAttrs && result.type !== Comment) {
  91. warn(
  92. `Extraneous non-props attributes (` +
  93. `${Object.keys(attrs).join(', ')}) ` +
  94. `were passed to component but could not be automatically inherited ` +
  95. `because component renders fragment or text root nodes.`
  96. )
  97. }
  98. }
  99. // inherit vnode hooks
  100. if (vnodeHooks !== EMPTY_OBJ) {
  101. result = cloneVNode(result, vnodeHooks)
  102. }
  103. // inherit scopeId
  104. const parentScopeId = parent && parent.type.__scopeId
  105. if (parentScopeId) {
  106. result = cloneVNode(result, { [parentScopeId]: '' })
  107. }
  108. // inherit directives
  109. if (vnode.dirs != null) {
  110. if (__DEV__ && !isElementRoot(result)) {
  111. warn(
  112. `Runtime directive used on component with non-element root node. ` +
  113. `The directives will not function as intended.`
  114. )
  115. }
  116. result.dirs = vnode.dirs
  117. }
  118. // inherit transition data
  119. if (vnode.transition != null) {
  120. if (__DEV__ && !isElementRoot(result)) {
  121. warn(
  122. `Component inside <Transition> renders non-element root node ` +
  123. `that cannot be animated.`
  124. )
  125. }
  126. result.transition = vnode.transition
  127. }
  128. } catch (err) {
  129. handleError(err, instance, ErrorCodes.RENDER_FUNCTION)
  130. result = createVNode(Comment)
  131. }
  132. currentRenderingInstance = null
  133. return result
  134. }
  135. const getFallthroughAttrs = (attrs: Data): Data | undefined => {
  136. let res: Data | undefined
  137. for (const key in attrs) {
  138. if (
  139. key === 'class' ||
  140. key === 'style' ||
  141. key === 'role' ||
  142. isOn(key) ||
  143. key.indexOf('aria-') === 0 ||
  144. key.indexOf('data-') === 0
  145. ) {
  146. ;(res || (res = {}))[key] = attrs[key]
  147. }
  148. }
  149. return res
  150. }
  151. const isElementRoot = (vnode: VNode) => {
  152. return (
  153. vnode.shapeFlag & ShapeFlags.COMPONENT ||
  154. vnode.shapeFlag & ShapeFlags.ELEMENT ||
  155. vnode.type === Comment // potential v-if branch switch
  156. )
  157. }
  158. export function shouldUpdateComponent(
  159. prevVNode: VNode,
  160. nextVNode: VNode,
  161. parentComponent: ComponentInternalInstance | null,
  162. optimized?: boolean
  163. ): boolean {
  164. const { props: prevProps, children: prevChildren } = prevVNode
  165. const { props: nextProps, children: nextChildren, patchFlag } = nextVNode
  166. // Parent component's render function was hot-updated. Since this may have
  167. // caused the child component's slots content to have changed, we need to
  168. // force the child to update as well.
  169. if (
  170. __BUNDLER__ &&
  171. __DEV__ &&
  172. (prevChildren || nextChildren) &&
  173. parentComponent &&
  174. parentComponent.renderUpdated
  175. ) {
  176. return true
  177. }
  178. // force child update on runtime directive usage on component vnode.
  179. if (nextVNode.dirs != null) {
  180. return true
  181. }
  182. if (patchFlag > 0) {
  183. if (patchFlag & PatchFlags.DYNAMIC_SLOTS) {
  184. // slot content that references values that might have changed,
  185. // e.g. in a v-for
  186. return true
  187. }
  188. if (patchFlag & PatchFlags.FULL_PROPS) {
  189. // presence of this flag indicates props are always non-null
  190. return hasPropsChanged(prevProps!, nextProps!)
  191. } else {
  192. if (patchFlag & PatchFlags.CLASS) {
  193. return prevProps!.class !== nextProps!.class
  194. }
  195. if (patchFlag & PatchFlags.STYLE) {
  196. return hasPropsChanged(prevProps!.style, nextProps!.style)
  197. }
  198. if (patchFlag & PatchFlags.PROPS) {
  199. const dynamicProps = nextVNode.dynamicProps!
  200. for (let i = 0; i < dynamicProps.length; i++) {
  201. const key = dynamicProps[i]
  202. if (nextProps![key] !== prevProps![key]) {
  203. return true
  204. }
  205. }
  206. }
  207. }
  208. } else if (!optimized) {
  209. // this path is only taken by manually written render functions
  210. // so presence of any children leads to a forced update
  211. if (prevChildren != null || nextChildren != null) {
  212. if (nextChildren == null || !(nextChildren as any).$stable) {
  213. return true
  214. }
  215. }
  216. if (prevProps === nextProps) {
  217. return false
  218. }
  219. if (prevProps === null) {
  220. return nextProps !== null
  221. }
  222. if (nextProps === null) {
  223. return true
  224. }
  225. return hasPropsChanged(prevProps, nextProps)
  226. }
  227. return false
  228. }
  229. function hasPropsChanged(prevProps: Data, nextProps: Data): boolean {
  230. const nextKeys = Object.keys(nextProps)
  231. if (nextKeys.length !== Object.keys(prevProps).length) {
  232. return true
  233. }
  234. for (let i = 0; i < nextKeys.length; i++) {
  235. const key = nextKeys[i]
  236. if (nextProps[key] !== prevProps[key]) {
  237. return true
  238. }
  239. }
  240. return false
  241. }
  242. export function updateHOCHostEl(
  243. { vnode, parent }: ComponentInternalInstance,
  244. el: typeof vnode.el // HostNode
  245. ) {
  246. while (parent && parent.subTree === vnode) {
  247. ;(vnode = parent.vnode).el = el
  248. parent = parent.parent
  249. }
  250. }