componentRenderUtils.ts 9.2 KB

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