vdom.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. import {
  2. ComponentInstance,
  3. ComponentClass,
  4. FunctionalComponent
  5. } from './component'
  6. import { VNodeFlags, ChildrenFlags } from './flags'
  7. import { createComponentClassFromOptions } from './componentOptions'
  8. import { EMPTY_OBJ, isObject, isArray, isFunction, isString } from '@vue/shared'
  9. import { RawChildrenType, RawSlots } from './h'
  10. import { FunctionalHandle } from './createRenderer'
  11. const handlersRE = /^on|^vnode/
  12. const STABLE_SLOTS_HINT = '$stable'
  13. // Vue core is platform agnostic, so we are not using Element for "DOM" nodes.
  14. export interface RenderNode {
  15. vnode?: VNode | null
  16. // technically this doesn't exist on platform render nodes,
  17. // but we list it here so that TS can figure out union types
  18. $f: false
  19. }
  20. export interface VNode {
  21. _isVNode: true
  22. flags: VNodeFlags
  23. tag: string | FunctionalComponent | ComponentClass | RenderNode | null
  24. data: VNodeData | null
  25. children: VNodeChildren
  26. childFlags: ChildrenFlags
  27. key: Key | null
  28. ref: Ref | null
  29. slots: Slots | null
  30. // only on mounted nodes
  31. el: RenderNode | null
  32. // only on mounted component nodes that is also a root node (HOCs)
  33. // points to parent component's placeholder vnode
  34. // this is used to update vnode.el for nested HOCs.
  35. parentVNode: VNode | null
  36. // only on mounted component nodes
  37. // points to the parent stateful/functional component's placeholder node
  38. contextVNode: VNode | null
  39. // only on mounted functional component nodes
  40. // a consistent handle so that a functional component can be identified
  41. // by the scheduler
  42. handle: FunctionalHandle | null
  43. // only on cloned vnodes, points to the original cloned vnode
  44. clonedFrom: VNode | null
  45. }
  46. export interface MountedVNode extends VNode {
  47. el: RenderNode
  48. }
  49. export interface BuiltInProps {
  50. key?: Key | null
  51. ref?: Ref | null
  52. slots?: RawSlots | null
  53. }
  54. export type VNodeData = {
  55. [key: string]: any
  56. } & BuiltInProps
  57. export type VNodeChildren =
  58. | VNode[] // ELEMENT | PORTAL
  59. | ComponentInstance // COMPONENT_STATEFUL
  60. | VNode // COMPONENT_FUNCTIONAL
  61. | string // TEXT
  62. | null
  63. export type Key = string | number
  64. export type Ref = (t: RenderNode | ComponentInstance | null) => void
  65. export type Slot = (...args: any[]) => VNode[]
  66. export type Slots = Readonly<{
  67. [name: string]: Slot
  68. }>
  69. export function createVNode(
  70. flags: VNodeFlags,
  71. tag: string | FunctionalComponent | ComponentClass | RenderNode | null,
  72. data: VNodeData | null,
  73. children: RawChildrenType | null,
  74. childFlags: ChildrenFlags,
  75. key: Key | null | undefined,
  76. ref: Ref | null | undefined,
  77. slots: Slots | null | undefined
  78. ): VNode {
  79. const vnode: VNode = {
  80. _isVNode: true,
  81. flags,
  82. tag,
  83. data,
  84. children: children as VNodeChildren,
  85. childFlags,
  86. key: key === void 0 ? null : key,
  87. ref: ref === void 0 ? null : ref,
  88. slots: slots === void 0 ? null : slots,
  89. el: null,
  90. parentVNode: null,
  91. contextVNode: null,
  92. handle: null,
  93. clonedFrom: null
  94. }
  95. if (childFlags === ChildrenFlags.UNKNOWN_CHILDREN) {
  96. normalizeChildren(vnode, children)
  97. }
  98. return vnode
  99. }
  100. export function createElementVNode(
  101. tag: string,
  102. data: VNodeData | null,
  103. children: RawChildrenType | null,
  104. childFlags: ChildrenFlags,
  105. key?: Key | null,
  106. ref?: Ref | null
  107. ) {
  108. const flags = tag === 'svg' ? VNodeFlags.ELEMENT_SVG : VNodeFlags.ELEMENT_HTML
  109. if (data !== null) {
  110. normalizeClassAndStyle(data)
  111. }
  112. return createVNode(flags, tag, data, children, childFlags, key, ref, null)
  113. }
  114. function normalizeClassAndStyle(data: VNodeData) {
  115. if (data.class != null) {
  116. data.class = normalizeClass(data.class)
  117. }
  118. if (data.style != null) {
  119. data.style = normalizeStyle(data.style)
  120. }
  121. }
  122. function normalizeStyle(value: any): Record<string, string | number> | void {
  123. if (isArray(value)) {
  124. const res: Record<string, string | number> = {}
  125. for (let i = 0; i < value.length; i++) {
  126. const normalized = normalizeStyle(value[i])
  127. if (normalized) {
  128. for (const key in normalized) {
  129. res[key] = normalized[key]
  130. }
  131. }
  132. }
  133. return res
  134. } else if (isObject(value)) {
  135. return value
  136. }
  137. }
  138. function normalizeClass(value: any): string {
  139. let res = ''
  140. if (isString(value)) {
  141. res = value
  142. } else if (isArray(value)) {
  143. for (let i = 0; i < value.length; i++) {
  144. res += normalizeClass(value[i]) + ' '
  145. }
  146. } else if (isObject(value)) {
  147. for (const name in value) {
  148. if (value[name]) {
  149. res += name + ' '
  150. }
  151. }
  152. }
  153. return res.trim()
  154. }
  155. export function createComponentVNode(
  156. comp: any,
  157. data: VNodeData | null,
  158. children: RawChildrenType | Slots,
  159. childFlags: ChildrenFlags,
  160. key?: Key | null,
  161. ref?: Ref | null
  162. ) {
  163. // resolve type
  164. let flags: VNodeFlags
  165. // flags
  166. if (isObject(comp)) {
  167. if (comp.functional) {
  168. // object literal functional
  169. flags = VNodeFlags.COMPONENT_FUNCTIONAL
  170. const { render } = comp
  171. if (!comp._normalized) {
  172. render.pure = comp.pure
  173. render.props = comp.props
  174. comp._normalized = true
  175. }
  176. comp = render
  177. } else {
  178. // object literal stateful
  179. flags = VNodeFlags.COMPONENT_STATEFUL_NORMAL
  180. comp =
  181. comp._normalized ||
  182. (comp._normalized = createComponentClassFromOptions(comp))
  183. }
  184. } else {
  185. // assumes comp is function here now
  186. if (__DEV__ && !isFunction(comp)) {
  187. // TODO warn invalid comp value in dev
  188. }
  189. if (comp.prototype && comp.prototype.render) {
  190. flags = VNodeFlags.COMPONENT_STATEFUL_NORMAL
  191. } else {
  192. flags = VNodeFlags.COMPONENT_FUNCTIONAL
  193. }
  194. }
  195. if (__DEV__ && flags === VNodeFlags.COMPONENT_FUNCTIONAL && ref) {
  196. // TODO warn functional component cannot have ref
  197. }
  198. // slots
  199. let slots: any
  200. if (childFlags === ChildrenFlags.STABLE_SLOTS) {
  201. slots = children
  202. } else if (childFlags === ChildrenFlags.UNKNOWN_CHILDREN) {
  203. childFlags = children
  204. ? ChildrenFlags.DYNAMIC_SLOTS
  205. : ChildrenFlags.NO_CHILDREN
  206. if (children != null) {
  207. if (isFunction(children)) {
  208. // function as children
  209. slots = { default: children }
  210. } else if (isObject(children) && !(children as any)._isVNode) {
  211. // slot object as children
  212. slots = children
  213. // special manual optimization hint for raw render fn users
  214. if (slots[STABLE_SLOTS_HINT]) {
  215. childFlags = ChildrenFlags.STABLE_SLOTS
  216. }
  217. } else {
  218. slots = { default: () => children }
  219. }
  220. slots = normalizeSlots(slots)
  221. }
  222. }
  223. // class & style
  224. if (data !== null) {
  225. normalizeClassAndStyle(data)
  226. }
  227. return createVNode(
  228. flags,
  229. comp,
  230. data,
  231. null, // to be set during mount
  232. childFlags,
  233. key,
  234. ref,
  235. slots
  236. )
  237. }
  238. export function createTextVNode(text: string): VNode {
  239. return createVNode(
  240. VNodeFlags.TEXT,
  241. null,
  242. null,
  243. text == null ? '' : text,
  244. ChildrenFlags.NO_CHILDREN,
  245. null,
  246. null,
  247. null
  248. )
  249. }
  250. export function createFragment(
  251. children: RawChildrenType,
  252. childFlags?: ChildrenFlags,
  253. key?: Key | null
  254. ) {
  255. return createVNode(
  256. VNodeFlags.FRAGMENT,
  257. null,
  258. null,
  259. children,
  260. childFlags === void 0 ? ChildrenFlags.UNKNOWN_CHILDREN : childFlags,
  261. key,
  262. null,
  263. null
  264. )
  265. }
  266. export function createPortal(
  267. target: RenderNode | string,
  268. children: RawChildrenType,
  269. childFlags?: ChildrenFlags,
  270. key?: Key | null,
  271. ref?: Ref | null
  272. ): VNode {
  273. return createVNode(
  274. VNodeFlags.PORTAL,
  275. target,
  276. null,
  277. children,
  278. childFlags === void 0 ? ChildrenFlags.UNKNOWN_CHILDREN : childFlags,
  279. key,
  280. ref,
  281. null
  282. )
  283. }
  284. export function cloneVNode(vnode: VNode, extraData?: VNodeData): VNode {
  285. const { flags, data } = vnode
  286. if (flags & VNodeFlags.ELEMENT || flags & VNodeFlags.COMPONENT) {
  287. let clonedData = data
  288. if (extraData != null) {
  289. clonedData = {}
  290. if (data != null) {
  291. for (const key in data) {
  292. clonedData[key] = data[key]
  293. }
  294. }
  295. if (extraData !== EMPTY_OBJ) {
  296. for (const key in extraData) {
  297. if (key === 'class') {
  298. clonedData.class = normalizeClass([
  299. clonedData.class,
  300. extraData.class
  301. ])
  302. } else if (key === 'style') {
  303. clonedData.style = normalizeStyle([
  304. clonedData.style,
  305. extraData.style
  306. ])
  307. } else if (handlersRE.test(key)) {
  308. // on*, vnode*
  309. const existing = clonedData[key]
  310. clonedData[key] = existing
  311. ? [].concat(existing, extraData[key])
  312. : extraData[key]
  313. } else {
  314. clonedData[key] = extraData[key]
  315. }
  316. }
  317. }
  318. }
  319. const cloned = createVNode(
  320. flags,
  321. vnode.tag,
  322. clonedData,
  323. vnode.children as RawChildrenType,
  324. vnode.childFlags,
  325. vnode.key,
  326. vnode.ref,
  327. vnode.slots
  328. )
  329. cloned.clonedFrom = vnode.clonedFrom || vnode
  330. return cloned
  331. } else if (flags & VNodeFlags.TEXT) {
  332. return createTextVNode(vnode.children as string)
  333. } else {
  334. return vnode
  335. }
  336. }
  337. function normalizeChildren(vnode: VNode, children: any) {
  338. let childFlags
  339. if (isArray(children)) {
  340. const { length } = children
  341. if (length === 0) {
  342. childFlags = ChildrenFlags.NO_CHILDREN
  343. children = null
  344. } else if (length === 1) {
  345. childFlags = ChildrenFlags.SINGLE_VNODE
  346. children = children[0]
  347. if (children.el) {
  348. children = cloneVNode(children)
  349. }
  350. } else {
  351. childFlags = ChildrenFlags.KEYED_VNODES
  352. children = normalizeVNodes(children)
  353. }
  354. } else if (children == null) {
  355. childFlags = ChildrenFlags.NO_CHILDREN
  356. } else if (children._isVNode) {
  357. childFlags = ChildrenFlags.SINGLE_VNODE
  358. if (children.el) {
  359. children = cloneVNode(children)
  360. }
  361. } else {
  362. // primitives or invalid values, cast to string
  363. childFlags = ChildrenFlags.SINGLE_VNODE
  364. children = createTextVNode(children + '')
  365. }
  366. vnode.children = children
  367. vnode.childFlags = childFlags
  368. }
  369. export function normalizeVNodes(
  370. children: any[],
  371. newChildren: VNode[] = [],
  372. currentPrefix: string = ''
  373. ): VNode[] {
  374. for (let i = 0; i < children.length; i++) {
  375. const child = children[i]
  376. let newChild
  377. if (child == null) {
  378. newChild = createTextVNode('')
  379. } else if (child._isVNode) {
  380. newChild = child.el ? cloneVNode(child) : child
  381. } else if (isArray(child)) {
  382. normalizeVNodes(child, newChildren, currentPrefix + i + '|')
  383. } else {
  384. newChild = createTextVNode(child + '')
  385. }
  386. if (newChild) {
  387. if (newChild.key == null) {
  388. newChild.key = currentPrefix + i
  389. }
  390. newChildren.push(newChild)
  391. }
  392. }
  393. return newChildren
  394. }
  395. // ensure all slot functions return Arrays
  396. function normalizeSlots(slots: { [name: string]: any }): Slots {
  397. if (slots._normalized) {
  398. return slots
  399. }
  400. const normalized = { _normalized: true } as any
  401. for (const name in slots) {
  402. if (name === STABLE_SLOTS_HINT) {
  403. continue
  404. }
  405. normalized[name] = (...args: any[]) => normalizeSlot(slots[name](...args))
  406. }
  407. return normalized
  408. }
  409. function normalizeSlot(value: any): VNode[] {
  410. if (value == null) {
  411. return [createTextVNode('')]
  412. } else if (isArray(value)) {
  413. return normalizeVNodes(value)
  414. } else if (value._isVNode) {
  415. return [value]
  416. } else {
  417. return [createTextVNode(value + '')]
  418. }
  419. }