vnode.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. import {
  2. isArray,
  3. isFunction,
  4. isString,
  5. isObject,
  6. EMPTY_ARR,
  7. extend
  8. } from '@vue/shared'
  9. import { ComponentInstance, Data, SetupProxySymbol } from './component'
  10. import { HostNode } from './createRenderer'
  11. import { RawSlots } from './componentSlots'
  12. import { PatchFlags } from './patchFlags'
  13. import { ShapeFlags } from './shapeFlags'
  14. import { isReactive } from '@vue/reactivity'
  15. import { AppContext } from './apiApp'
  16. export const Fragment = Symbol('Fragment')
  17. export const Text = Symbol('Text')
  18. export const Empty = Symbol('Empty')
  19. export const Portal = Symbol('Portal')
  20. export type VNodeTypes =
  21. | string
  22. | Function
  23. | Object
  24. | typeof Fragment
  25. | typeof Portal
  26. | typeof Text
  27. | typeof Empty
  28. type VNodeChildAtom = VNode | string | number | boolean | null | void
  29. export interface VNodeChildren extends Array<VNodeChildren | VNodeChildAtom> {}
  30. export type VNodeChild = VNodeChildAtom | VNodeChildren
  31. export type NormalizedChildren = string | VNodeChildren | RawSlots | null
  32. export interface VNode {
  33. type: VNodeTypes
  34. props: Record<any, any> | null
  35. key: string | number | null
  36. ref: string | Function | null
  37. children: NormalizedChildren
  38. component: ComponentInstance | null
  39. // DOM
  40. el: HostNode | null
  41. anchor: HostNode | null // fragment anchor
  42. target: HostNode | null // portal target
  43. // optimization only
  44. shapeFlag: number
  45. patchFlag: number
  46. dynamicProps: string[] | null
  47. dynamicChildren: VNode[] | null
  48. // application root node only
  49. appContext: AppContext | null
  50. }
  51. // Since v-if and v-for are the two possible ways node structure can dynamically
  52. // change, once we consider v-if branches and each v-for fragment a block, we
  53. // can divide a template into nested blocks, and within each block the node
  54. // structure would be stable. This allows us to skip most children diffing
  55. // and only worry about the dynamic nodes (indicated by patch flags).
  56. const blockStack: (VNode[] | null)[] = []
  57. // Open a block.
  58. // This must be called before `createBlock`. It cannot be part of `createBlock`
  59. // because the children of the block are evaluated before `createBlock` itself
  60. // is called. The generated code typically looks like this:
  61. //
  62. // function render() {
  63. // return (openBlock(),createBlock('div', null, [...]))
  64. // }
  65. //
  66. // disableTracking is true when creating a fragment block, since a fragment
  67. // always diffs its children.
  68. export function openBlock(disableTrackng?: boolean) {
  69. blockStack.push(disableTrackng ? null : [])
  70. }
  71. let shouldTrack = true
  72. // Create a block root vnode. Takes the same exact arguments as `createVNode`.
  73. // A block root keeps track of dynamic nodes within the block in the
  74. // `dynamicChildren` array.
  75. export function createBlock(
  76. type: VNodeTypes,
  77. props?: { [key: string]: any } | null,
  78. children?: any,
  79. patchFlag?: number,
  80. dynamicProps?: string[]
  81. ): VNode {
  82. // avoid a block with optFlag tracking itself
  83. shouldTrack = false
  84. const vnode = createVNode(type, props, children, patchFlag, dynamicProps)
  85. shouldTrack = true
  86. const trackedNodes = blockStack.pop()
  87. vnode.dynamicChildren =
  88. trackedNodes && trackedNodes.length ? trackedNodes : EMPTY_ARR
  89. // a block is always going to be patched
  90. trackDynamicNode(vnode)
  91. return vnode
  92. }
  93. export function createVNode(
  94. type: VNodeTypes,
  95. props: { [key: string]: any } | null | 0 = null,
  96. children: any = null,
  97. patchFlag: number = 0,
  98. dynamicProps: string[] | null = null
  99. ): VNode {
  100. // Allow passing 0 for props, this can save bytes on generated code.
  101. props = props || null
  102. // class & style normalization.
  103. if (props !== null) {
  104. // for reactive or proxy objects, we need to clone it to enable mutation.
  105. if (isReactive(props) || SetupProxySymbol in props) {
  106. props = extend({}, props)
  107. }
  108. // class normalization only needed if the vnode isn't generated by
  109. // compiler-optimized code
  110. if (props.class != null && !(patchFlag & PatchFlags.CLASS)) {
  111. props.class = normalizeClass(props.class)
  112. }
  113. let { style } = props
  114. if (style != null) {
  115. // reactive state objects need to be cloned since they are likely to be
  116. // mutated
  117. if (isReactive(style) && !isArray(style)) {
  118. style = extend({}, style)
  119. }
  120. props.style = normalizeStyle(style)
  121. }
  122. }
  123. // encode the vnode type information into a bitmap
  124. const shapeFlag = isString(type)
  125. ? ShapeFlags.ELEMENT
  126. : isObject(type)
  127. ? ShapeFlags.STATEFUL_COMPONENT
  128. : isFunction(type)
  129. ? ShapeFlags.FUNCTIONAL_COMPONENT
  130. : 0
  131. const vnode: VNode = {
  132. type,
  133. props,
  134. key: (props && props.key) || null,
  135. ref: (props && props.ref) || null,
  136. children: null,
  137. component: null,
  138. el: null,
  139. anchor: null,
  140. target: null,
  141. shapeFlag,
  142. patchFlag,
  143. dynamicProps,
  144. dynamicChildren: null,
  145. appContext: null
  146. }
  147. normalizeChildren(vnode, children)
  148. // presence of a patch flag indicates this node needs patching on updates.
  149. // component nodes also should always be patched, because even if the
  150. // component doesn't need to update, it needs to persist the instance on to
  151. // the next vnode so that it can be properly unmounted later.
  152. if (
  153. shouldTrack &&
  154. (patchFlag ||
  155. shapeFlag & ShapeFlags.STATEFUL_COMPONENT ||
  156. shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT)
  157. ) {
  158. trackDynamicNode(vnode)
  159. }
  160. return vnode
  161. }
  162. function trackDynamicNode(vnode: VNode) {
  163. const currentBlockDynamicNodes = blockStack[blockStack.length - 1]
  164. if (currentBlockDynamicNodes != null) {
  165. currentBlockDynamicNodes.push(vnode)
  166. }
  167. }
  168. export function cloneVNode(vnode: VNode): VNode {
  169. return {
  170. type: vnode.type,
  171. props: vnode.props,
  172. key: vnode.key,
  173. ref: vnode.ref,
  174. children: vnode.children,
  175. target: vnode.target,
  176. shapeFlag: vnode.shapeFlag,
  177. patchFlag: vnode.patchFlag,
  178. dynamicProps: vnode.dynamicProps,
  179. dynamicChildren: vnode.dynamicChildren,
  180. appContext: vnode.appContext,
  181. // these should be set to null since they should only be present on
  182. // mounted VNodes. If they are somehow not null, this means we have
  183. // encountered an already-mounted vnode being used again.
  184. component: null,
  185. el: null,
  186. anchor: null
  187. }
  188. }
  189. export function normalizeVNode(child: VNodeChild): VNode {
  190. if (child == null) {
  191. // empty placeholder
  192. return createVNode(Empty)
  193. } else if (isArray(child)) {
  194. // fragment
  195. return createVNode(Fragment, null, child)
  196. } else if (typeof child === 'object') {
  197. // already vnode, this should be the most common since compiled templates
  198. // always produce all-vnode children arrays
  199. return child.el === null ? child : cloneVNode(child)
  200. } else {
  201. // primitive types
  202. return createVNode(Text, null, child + '')
  203. }
  204. }
  205. export function normalizeChildren(vnode: VNode, children: unknown) {
  206. let type = 0
  207. if (children == null) {
  208. children = null
  209. } else if (isArray(children)) {
  210. type = ShapeFlags.ARRAY_CHILDREN
  211. } else if (typeof children === 'object') {
  212. type = ShapeFlags.SLOTS_CHILDREN
  213. } else if (isFunction(children)) {
  214. children = { default: children }
  215. type = ShapeFlags.SLOTS_CHILDREN
  216. } else {
  217. children = isString(children) ? children : children + ''
  218. type = ShapeFlags.TEXT_CHILDREN
  219. }
  220. vnode.children = children as NormalizedChildren
  221. vnode.shapeFlag |= type
  222. }
  223. function normalizeStyle(
  224. value: unknown
  225. ): Record<string, string | number> | void {
  226. if (isArray(value)) {
  227. const res: Record<string, string | number> = {}
  228. for (let i = 0; i < value.length; i++) {
  229. const normalized = normalizeStyle(value[i])
  230. if (normalized) {
  231. for (const key in normalized) {
  232. res[key] = normalized[key]
  233. }
  234. }
  235. }
  236. return res
  237. } else if (isObject(value)) {
  238. return value
  239. }
  240. }
  241. export function normalizeClass(value: unknown): string {
  242. let res = ''
  243. if (isString(value)) {
  244. res = value
  245. } else if (isArray(value)) {
  246. for (let i = 0; i < value.length; i++) {
  247. res += normalizeClass(value[i]) + ' '
  248. }
  249. } else if (isObject(value)) {
  250. for (const name in value) {
  251. if (value[name]) {
  252. res += name + ' '
  253. }
  254. }
  255. }
  256. return res.trim()
  257. }
  258. const handlersRE = /^on|^vnode/
  259. export function mergeProps(...args: Data[]) {
  260. const ret: Data = {}
  261. extend(ret, args[0])
  262. for (let i = 1; i < args.length; i++) {
  263. const toMerge = args[i]
  264. for (const key in toMerge) {
  265. if (key === 'class') {
  266. ret.class = normalizeClass([ret.class, toMerge.class])
  267. } else if (key === 'style') {
  268. ret.style = normalizeStyle([ret.style, toMerge.style])
  269. } else if (handlersRE.test(key)) {
  270. // on*, vnode*
  271. const existing = ret[key]
  272. ret[key] = existing
  273. ? [].concat(existing as any, toMerge[key] as any)
  274. : toMerge[key]
  275. } else {
  276. ret[key] = toMerge[key]
  277. }
  278. }
  279. }
  280. return ret
  281. }