vnode.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. import {
  2. isArray,
  3. isFunction,
  4. isString,
  5. isObject,
  6. EMPTY_ARR,
  7. extend,
  8. normalizeClass,
  9. normalizeStyle,
  10. PatchFlags,
  11. ShapeFlags
  12. } from '@vue/shared'
  13. import {
  14. ComponentInternalInstance,
  15. Data,
  16. SetupProxySymbol,
  17. Component
  18. } from './component'
  19. import { RawSlots } from './componentSlots'
  20. import { isReactive, Ref } from '@vue/reactivity'
  21. import { AppContext } from './apiCreateApp'
  22. import {
  23. SuspenseImpl,
  24. isSuspense,
  25. SuspenseBoundary
  26. } from './components/Suspense'
  27. import { DirectiveBinding } from './directives'
  28. import { TransitionHooks } from './components/BaseTransition'
  29. import { warn } from './warning'
  30. import { currentScopeId } from './helpers/scopeId'
  31. import { PortalImpl, isPortal } from './components/Portal'
  32. export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
  33. __isFragment: true
  34. new (): {
  35. $props: VNodeProps
  36. }
  37. }
  38. export const Text = Symbol(__DEV__ ? 'Text' : undefined)
  39. export const Comment = Symbol(__DEV__ ? 'Comment' : undefined)
  40. export const Static = Symbol(__DEV__ ? 'Static' : undefined)
  41. export type VNodeTypes =
  42. | string
  43. | Component
  44. | typeof Text
  45. | typeof Static
  46. | typeof Comment
  47. | typeof Fragment
  48. | typeof PortalImpl
  49. | typeof SuspenseImpl
  50. export interface VNodeProps {
  51. [key: string]: any
  52. key?: string | number
  53. ref?: string | Ref | ((ref: object | null) => void)
  54. // vnode hooks
  55. onVnodeBeforeMount?: (vnode: VNode) => void
  56. onVnodeMounted?: (vnode: VNode) => void
  57. onVnodeBeforeUpdate?: (vnode: VNode, oldVNode: VNode) => void
  58. onVnodeUpdated?: (vnode: VNode, oldVNode: VNode) => void
  59. onVnodeBeforeUnmount?: (vnode: VNode) => void
  60. onVnodeUnmounted?: (vnode: VNode) => void
  61. }
  62. type VNodeChildAtom<HostNode, HostElement> =
  63. | VNode<HostNode, HostElement>
  64. | string
  65. | number
  66. | boolean
  67. | null
  68. | void
  69. export interface VNodeArrayChildren<HostNode = any, HostElement = any>
  70. extends Array<
  71. | VNodeArrayChildren<HostNode, HostElement>
  72. | VNodeChildAtom<HostNode, HostElement>
  73. > {}
  74. export type VNodeChild<HostNode = any, HostElement = any> =
  75. | VNodeChildAtom<HostNode, HostElement>
  76. | VNodeArrayChildren<HostNode, HostElement>
  77. export type VNodeNormalizedChildren<HostNode = any, HostElement = any> =
  78. | string
  79. | VNodeArrayChildren<HostNode, HostElement>
  80. | RawSlots
  81. | null
  82. export interface VNode<HostNode = any, HostElement = any> {
  83. _isVNode: true
  84. type: VNodeTypes
  85. props: VNodeProps | null
  86. key: string | number | null
  87. ref: string | Ref | ((ref: object | null) => void) | null
  88. scopeId: string | null // SFC only
  89. children: VNodeNormalizedChildren<HostNode, HostElement>
  90. component: ComponentInternalInstance | null
  91. suspense: SuspenseBoundary<HostNode, HostElement> | null
  92. dirs: DirectiveBinding[] | null
  93. transition: TransitionHooks | null
  94. // DOM
  95. el: HostNode | null
  96. anchor: HostNode | null // fragment anchor
  97. target: HostElement | null // portal target
  98. // optimization only
  99. shapeFlag: number
  100. patchFlag: number
  101. dynamicProps: string[] | null
  102. dynamicChildren: VNode[] | null
  103. // application root node only
  104. appContext: AppContext | null
  105. }
  106. // Since v-if and v-for are the two possible ways node structure can dynamically
  107. // change, once we consider v-if branches and each v-for fragment a block, we
  108. // can divide a template into nested blocks, and within each block the node
  109. // structure would be stable. This allows us to skip most children diffing
  110. // and only worry about the dynamic nodes (indicated by patch flags).
  111. const blockStack: (VNode[] | null)[] = []
  112. let currentBlock: VNode[] | null = null
  113. // Open a block.
  114. // This must be called before `createBlock`. It cannot be part of `createBlock`
  115. // because the children of the block are evaluated before `createBlock` itself
  116. // is called. The generated code typically looks like this:
  117. //
  118. // function render() {
  119. // return (openBlock(),createBlock('div', null, [...]))
  120. // }
  121. //
  122. // disableTracking is true when creating a fragment block, since a fragment
  123. // always diffs its children.
  124. export function openBlock(disableTracking = false) {
  125. blockStack.push((currentBlock = disableTracking ? null : []))
  126. }
  127. // Whether we should be tracking dynamic child nodes inside a block.
  128. // Only tracks when this value is > 0
  129. // We are not using a simple boolean because this value may need to be
  130. // incremented/decremented by nested usage of v-once (see below)
  131. let shouldTrack = 1
  132. // Block tracking sometimes needs to be disabled, for example during the
  133. // creation of a tree that needs to be cached by v-once. The compiler generates
  134. // code like this:
  135. // _cache[1] || (
  136. // setBlockTracking(-1),
  137. // _cache[1] = createVNode(...),
  138. // setBlockTracking(1),
  139. // _cache[1]
  140. // )
  141. export function setBlockTracking(value: number) {
  142. shouldTrack += value
  143. }
  144. // Create a block root vnode. Takes the same exact arguments as `createVNode`.
  145. // A block root keeps track of dynamic nodes within the block in the
  146. // `dynamicChildren` array.
  147. export function createBlock(
  148. type: VNodeTypes,
  149. props?: { [key: string]: any } | null,
  150. children?: any,
  151. patchFlag?: number,
  152. dynamicProps?: string[]
  153. ): VNode {
  154. // avoid a block with patchFlag tracking itself
  155. shouldTrack--
  156. const vnode = createVNode(type, props, children, patchFlag, dynamicProps)
  157. shouldTrack++
  158. // save current block children on the block vnode
  159. vnode.dynamicChildren = currentBlock || EMPTY_ARR
  160. // close block
  161. blockStack.pop()
  162. currentBlock = blockStack[blockStack.length - 1] || null
  163. // a block is always going to be patched, so track it as a child of its
  164. // parent block
  165. if (currentBlock !== null) {
  166. currentBlock.push(vnode)
  167. }
  168. return vnode
  169. }
  170. export function isVNode(value: any): value is VNode {
  171. return value ? value._isVNode === true : false
  172. }
  173. export function isSameVNodeType(n1: VNode, n2: VNode): boolean {
  174. if (
  175. __BUNDLER__ &&
  176. __DEV__ &&
  177. n2.shapeFlag & ShapeFlags.COMPONENT &&
  178. (n2.type as Component).__hmrUpdated
  179. ) {
  180. // HMR only: if the component has been hot-updated, force a reload.
  181. return false
  182. }
  183. return n1.type === n2.type && n1.key === n2.key
  184. }
  185. export function createVNode(
  186. type: VNodeTypes,
  187. props: (Data & VNodeProps) | null = null,
  188. children: unknown = null,
  189. patchFlag: number = 0,
  190. dynamicProps: string[] | null = null
  191. ): VNode {
  192. if (__DEV__ && !type) {
  193. warn(`Invalid vnode type when creating vnode: ${type}.`)
  194. type = Comment
  195. }
  196. // class & style normalization.
  197. if (props !== null) {
  198. // for reactive or proxy objects, we need to clone it to enable mutation.
  199. if (isReactive(props) || SetupProxySymbol in props) {
  200. props = extend({}, props)
  201. }
  202. let { class: klass, style } = props
  203. if (klass != null && !isString(klass)) {
  204. props.class = normalizeClass(klass)
  205. }
  206. if (isObject(style)) {
  207. // reactive state objects need to be cloned since they are likely to be
  208. // mutated
  209. if (isReactive(style) && !isArray(style)) {
  210. style = extend({}, style)
  211. }
  212. props.style = normalizeStyle(style)
  213. }
  214. }
  215. // encode the vnode type information into a bitmap
  216. const shapeFlag = isString(type)
  217. ? ShapeFlags.ELEMENT
  218. : __FEATURE_SUSPENSE__ && isSuspense(type)
  219. ? ShapeFlags.SUSPENSE
  220. : isPortal(type)
  221. ? ShapeFlags.PORTAL
  222. : isObject(type)
  223. ? ShapeFlags.STATEFUL_COMPONENT
  224. : isFunction(type)
  225. ? ShapeFlags.FUNCTIONAL_COMPONENT
  226. : 0
  227. const vnode: VNode = {
  228. _isVNode: true,
  229. type,
  230. props,
  231. key: (props !== null && props.key) || null,
  232. ref: (props !== null && props.ref) || null,
  233. scopeId: currentScopeId,
  234. children: null,
  235. component: null,
  236. suspense: null,
  237. dirs: null,
  238. transition: null,
  239. el: null,
  240. anchor: null,
  241. target: null,
  242. shapeFlag,
  243. patchFlag,
  244. dynamicProps,
  245. dynamicChildren: null,
  246. appContext: null
  247. }
  248. normalizeChildren(vnode, children)
  249. // presence of a patch flag indicates this node needs patching on updates.
  250. // component nodes also should always be patched, because even if the
  251. // component doesn't need to update, it needs to persist the instance on to
  252. // the next vnode so that it can be properly unmounted later.
  253. if (
  254. shouldTrack > 0 &&
  255. currentBlock !== null &&
  256. // the EVENTS flag is only for hydration and if it is the only flag, the
  257. // vnode should not be considered dynamic due to handler caching.
  258. patchFlag !== PatchFlags.HYDRATE_EVENTS &&
  259. (patchFlag > 0 ||
  260. shapeFlag & ShapeFlags.SUSPENSE ||
  261. shapeFlag & ShapeFlags.STATEFUL_COMPONENT ||
  262. shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT)
  263. ) {
  264. currentBlock.push(vnode)
  265. }
  266. return vnode
  267. }
  268. export function cloneVNode<T, U>(
  269. vnode: VNode<T, U>,
  270. extraProps?: Data & VNodeProps
  271. ): VNode<T, U> {
  272. // This is intentionally NOT using spread or extend to avoid the runtime
  273. // key enumeration cost.
  274. return {
  275. _isVNode: true,
  276. type: vnode.type,
  277. props: extraProps
  278. ? vnode.props
  279. ? mergeProps(vnode.props, extraProps)
  280. : extraProps
  281. : vnode.props,
  282. key: vnode.key,
  283. ref: vnode.ref,
  284. scopeId: vnode.scopeId,
  285. children: vnode.children,
  286. target: vnode.target,
  287. shapeFlag: vnode.shapeFlag,
  288. patchFlag: vnode.patchFlag,
  289. dynamicProps: vnode.dynamicProps,
  290. dynamicChildren: vnode.dynamicChildren,
  291. appContext: vnode.appContext,
  292. dirs: vnode.dirs,
  293. transition: vnode.transition,
  294. // These should technically only be non-null on mounted VNodes. However,
  295. // they *should* be copied for kept-alive vnodes. So we just always copy
  296. // them since them being non-null during a mount doesn't affect the logic as
  297. // they will simply be overwritten.
  298. component: vnode.component,
  299. suspense: vnode.suspense,
  300. el: vnode.el,
  301. anchor: vnode.anchor
  302. }
  303. }
  304. export function createTextVNode(text: string = ' ', flag: number = 0): VNode {
  305. return createVNode(Text, null, text, flag)
  306. }
  307. export function createStaticVNode(content: string): VNode {
  308. return createVNode(Static, null, content)
  309. }
  310. export function createCommentVNode(
  311. text: string = '',
  312. // when used as the v-else branch, the comment node must be created as a
  313. // block to ensure correct updates.
  314. asBlock: boolean = false
  315. ): VNode {
  316. return asBlock
  317. ? (openBlock(), createBlock(Comment, null, text))
  318. : createVNode(Comment, null, text)
  319. }
  320. export function normalizeVNode<T, U>(child: VNodeChild<T, U>): VNode<T, U> {
  321. if (child == null || typeof child === 'boolean') {
  322. // empty placeholder
  323. return createVNode(Comment)
  324. } else if (isArray(child)) {
  325. // fragment
  326. return createVNode(Fragment, null, child)
  327. } else if (typeof child === 'object') {
  328. // already vnode, this should be the most common since compiled templates
  329. // always produce all-vnode children arrays
  330. return child.el === null ? child : cloneVNode(child)
  331. } else {
  332. // strings and numbers
  333. return createVNode(Text, null, String(child))
  334. }
  335. }
  336. // optimized normalization for template-compiled render fns
  337. export function cloneIfMounted(child: VNode): VNode {
  338. return child.el === null ? child : cloneVNode(child)
  339. }
  340. export function normalizeChildren(vnode: VNode, children: unknown) {
  341. let type = 0
  342. if (children == null) {
  343. children = null
  344. } else if (isArray(children)) {
  345. type = ShapeFlags.ARRAY_CHILDREN
  346. } else if (typeof children === 'object') {
  347. type = ShapeFlags.SLOTS_CHILDREN
  348. } else if (isFunction(children)) {
  349. children = { default: children }
  350. type = ShapeFlags.SLOTS_CHILDREN
  351. } else {
  352. children = String(children)
  353. type = ShapeFlags.TEXT_CHILDREN
  354. }
  355. vnode.children = children as VNodeNormalizedChildren
  356. vnode.shapeFlag |= type
  357. }
  358. const handlersRE = /^on|^vnode/
  359. export function mergeProps(...args: (Data & VNodeProps)[]) {
  360. const ret: Data = {}
  361. extend(ret, args[0])
  362. for (let i = 1; i < args.length; i++) {
  363. const toMerge = args[i]
  364. for (const key in toMerge) {
  365. if (key === 'class') {
  366. ret.class = normalizeClass([ret.class, toMerge.class])
  367. } else if (key === 'style') {
  368. ret.style = normalizeStyle([ret.style, toMerge.style])
  369. } else if (handlersRE.test(key)) {
  370. // on*, vnode*
  371. const existing = ret[key]
  372. ret[key] = existing
  373. ? [].concat(existing as any, toMerge[key] as any)
  374. : toMerge[key]
  375. } else {
  376. ret[key] = toMerge[key]
  377. }
  378. }
  379. }
  380. return ret
  381. }