vnode.ts 8.3 KB

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