apiCreateFor.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. import {
  2. EffectScope,
  3. type ShallowRef,
  4. isReactive,
  5. isShallow,
  6. pauseTracking,
  7. resetTracking,
  8. shallowReadArray,
  9. shallowRef,
  10. toReactive,
  11. } from '@vue/reactivity'
  12. import {
  13. FOR_ANCHOR_LABEL,
  14. getSequence,
  15. isArray,
  16. isObject,
  17. isString,
  18. } from '@vue/shared'
  19. import {
  20. createComment,
  21. createTextNode,
  22. findVaporFragmentAnchor,
  23. } from './dom/node'
  24. import {
  25. type Block,
  26. VaporFragment,
  27. insert,
  28. remove as removeBlock,
  29. } from './block'
  30. import { warn } from '@vue/runtime-dom'
  31. import { currentInstance, isVaporComponent } from './component'
  32. import type { DynamicSlot } from './componentSlots'
  33. import { renderEffect } from './renderEffect'
  34. import { VaporVForFlags } from '../../shared/src/vaporFlags'
  35. import {
  36. currentHydrationNode,
  37. isHydrating,
  38. locateHydrationNode,
  39. } from './dom/hydration'
  40. import {
  41. insertionAnchor,
  42. insertionParent,
  43. resetInsertionState,
  44. } from './insertionState'
  45. class ForBlock extends VaporFragment {
  46. scope: EffectScope | undefined
  47. key: any
  48. itemRef: ShallowRef<any>
  49. keyRef: ShallowRef<any> | undefined
  50. indexRef: ShallowRef<number | undefined> | undefined
  51. constructor(
  52. nodes: Block,
  53. scope: EffectScope | undefined,
  54. item: ShallowRef<any>,
  55. key: ShallowRef<any> | undefined,
  56. index: ShallowRef<number | undefined> | undefined,
  57. renderKey: any,
  58. ) {
  59. super(nodes)
  60. this.scope = scope
  61. this.itemRef = item
  62. this.keyRef = key
  63. this.indexRef = index
  64. this.key = renderKey
  65. }
  66. }
  67. type Source = any[] | Record<any, any> | number | Set<any> | Map<any, any>
  68. type ResolvedSource = {
  69. values: any[]
  70. needsWrap: boolean
  71. keys?: string[]
  72. }
  73. export const createFor = (
  74. src: () => Source,
  75. renderItem: (
  76. item: ShallowRef<any>,
  77. key: ShallowRef<any>,
  78. index: ShallowRef<number | undefined>,
  79. ) => Block,
  80. getKey?: (item: any, key: any, index?: number) => any,
  81. flags = 0,
  82. ): VaporFragment => {
  83. const _insertionParent = insertionParent
  84. const _insertionAnchor = insertionAnchor
  85. if (isHydrating) {
  86. locateHydrationNode(true)
  87. } else {
  88. resetInsertionState()
  89. }
  90. let isMounted = false
  91. let oldBlocks: ForBlock[] = []
  92. let newBlocks: ForBlock[]
  93. let parent: ParentNode | undefined | null
  94. let parentAnchor: Node
  95. if (isHydrating) {
  96. parentAnchor = findVaporFragmentAnchor(
  97. currentHydrationNode!,
  98. FOR_ANCHOR_LABEL,
  99. )!
  100. if (__DEV__ && !parentAnchor) {
  101. // TODO warn, should not happen
  102. warn(`createFor anchor not found...`)
  103. }
  104. } else {
  105. parentAnchor = __DEV__ ? createComment('for') : createTextNode()
  106. }
  107. const frag = new VaporFragment(oldBlocks)
  108. const instance = currentInstance!
  109. const canUseFastRemove = flags & VaporVForFlags.FAST_REMOVE
  110. const isComponent = flags & VaporVForFlags.IS_COMPONENT
  111. if (__DEV__ && !instance) {
  112. warn('createFor() can only be used inside setup()')
  113. }
  114. const renderList = () => {
  115. const source = normalizeSource(src())
  116. const newLength = source.values.length
  117. const oldLength = oldBlocks.length
  118. newBlocks = new Array(newLength)
  119. pauseTracking()
  120. if (!isMounted) {
  121. isMounted = true
  122. for (let i = 0; i < newLength; i++) {
  123. mount(source, i)
  124. }
  125. } else {
  126. parent = parent || parentAnchor!.parentNode
  127. if (!oldLength) {
  128. // fast path for all new
  129. for (let i = 0; i < newLength; i++) {
  130. mount(source, i)
  131. }
  132. } else if (!newLength) {
  133. // fast path for clearing all
  134. const doRemove = !canUseFastRemove
  135. for (let i = 0; i < oldLength; i++) {
  136. unmount(oldBlocks[i], doRemove)
  137. }
  138. if (canUseFastRemove) {
  139. parent!.textContent = ''
  140. parent!.appendChild(parentAnchor)
  141. }
  142. } else if (!getKey) {
  143. // unkeyed fast path
  144. const commonLength = Math.min(newLength, oldLength)
  145. for (let i = 0; i < commonLength; i++) {
  146. update((newBlocks[i] = oldBlocks[i]), getItem(source, i)[0])
  147. }
  148. for (let i = oldLength; i < newLength; i++) {
  149. mount(source, i)
  150. }
  151. for (let i = newLength; i < oldLength; i++) {
  152. unmount(oldBlocks[i])
  153. }
  154. } else {
  155. let i = 0
  156. let e1 = oldLength - 1 // prev ending index
  157. let e2 = newLength - 1 // next ending index
  158. // 1. sync from start
  159. // (a b) c
  160. // (a b) d e
  161. while (i <= e1 && i <= e2) {
  162. if (tryPatchIndex(source, i)) {
  163. i++
  164. } else {
  165. break
  166. }
  167. }
  168. // 2. sync from end
  169. // a (b c)
  170. // d e (b c)
  171. while (i <= e1 && i <= e2) {
  172. if (tryPatchIndex(source, i)) {
  173. e1--
  174. e2--
  175. } else {
  176. break
  177. }
  178. }
  179. // 3. common sequence + mount
  180. // (a b)
  181. // (a b) c
  182. // i = 2, e1 = 1, e2 = 2
  183. // (a b)
  184. // c (a b)
  185. // i = 0, e1 = -1, e2 = 0
  186. if (i > e1) {
  187. if (i <= e2) {
  188. const nextPos = e2 + 1
  189. const anchor =
  190. nextPos < newLength
  191. ? normalizeAnchor(newBlocks[nextPos].nodes)
  192. : parentAnchor
  193. while (i <= e2) {
  194. mount(source, i, anchor)
  195. i++
  196. }
  197. }
  198. }
  199. // 4. common sequence + unmount
  200. // (a b) c
  201. // (a b)
  202. // i = 2, e1 = 2, e2 = 1
  203. // a (b c)
  204. // (b c)
  205. // i = 0, e1 = 0, e2 = -1
  206. else if (i > e2) {
  207. while (i <= e1) {
  208. unmount(oldBlocks[i])
  209. i++
  210. }
  211. }
  212. // 5. unknown sequence
  213. // [i ... e1 + 1]: a b [c d e] f g
  214. // [i ... e2 + 1]: a b [e d c h] f g
  215. // i = 2, e1 = 4, e2 = 5
  216. else {
  217. const s1 = i // prev starting index
  218. const s2 = i // next starting index
  219. // 5.1 build key:index map for newChildren
  220. const keyToNewIndexMap = new Map()
  221. for (i = s2; i <= e2; i++) {
  222. keyToNewIndexMap.set(getKey(...getItem(source, i)), i)
  223. }
  224. // 5.2 loop through old children left to be patched and try to patch
  225. // matching nodes & remove nodes that are no longer present
  226. let j
  227. let patched = 0
  228. const toBePatched = e2 - s2 + 1
  229. let moved = false
  230. // used to track whether any node has moved
  231. let maxNewIndexSoFar = 0
  232. // works as Map<newIndex, oldIndex>
  233. // Note that oldIndex is offset by +1
  234. // and oldIndex = 0 is a special value indicating the new node has
  235. // no corresponding old node.
  236. // used for determining longest stable subsequence
  237. const newIndexToOldIndexMap = new Array(toBePatched).fill(0)
  238. for (i = s1; i <= e1; i++) {
  239. const prevBlock = oldBlocks[i]
  240. if (patched >= toBePatched) {
  241. // all new children have been patched so this can only be a removal
  242. unmount(prevBlock)
  243. } else {
  244. const newIndex = keyToNewIndexMap.get(prevBlock.key)
  245. if (newIndex == null) {
  246. unmount(prevBlock)
  247. } else {
  248. newIndexToOldIndexMap[newIndex - s2] = i + 1
  249. if (newIndex >= maxNewIndexSoFar) {
  250. maxNewIndexSoFar = newIndex
  251. } else {
  252. moved = true
  253. }
  254. update(
  255. (newBlocks[newIndex] = prevBlock),
  256. ...getItem(source, newIndex),
  257. )
  258. patched++
  259. }
  260. }
  261. }
  262. // 5.3 move and mount
  263. // generate longest stable subsequence only when nodes have moved
  264. const increasingNewIndexSequence = moved
  265. ? getSequence(newIndexToOldIndexMap)
  266. : []
  267. j = increasingNewIndexSequence.length - 1
  268. // looping backwards so that we can use last patched node as anchor
  269. for (i = toBePatched - 1; i >= 0; i--) {
  270. const nextIndex = s2 + i
  271. const anchor =
  272. nextIndex + 1 < newLength
  273. ? normalizeAnchor(newBlocks[nextIndex + 1].nodes)
  274. : parentAnchor
  275. if (newIndexToOldIndexMap[i] === 0) {
  276. // mount new
  277. mount(source, nextIndex, anchor)
  278. } else if (moved) {
  279. // move if:
  280. // There is no stable subsequence (e.g. a reverse)
  281. // OR current node is not among the stable sequence
  282. if (j < 0 || i !== increasingNewIndexSequence[j]) {
  283. insert(newBlocks[nextIndex].nodes, parent!, anchor)
  284. } else {
  285. j--
  286. }
  287. }
  288. }
  289. }
  290. }
  291. }
  292. frag.nodes = [(oldBlocks = newBlocks)]
  293. if (parentAnchor) {
  294. frag.nodes.push(parentAnchor)
  295. }
  296. resetTracking()
  297. }
  298. const needKey = renderItem.length > 1
  299. const needIndex = renderItem.length > 2
  300. const mount = (
  301. source: ResolvedSource,
  302. idx: number,
  303. anchor: Node | undefined = parentAnchor,
  304. ): ForBlock => {
  305. const [item, key, index] = getItem(source, idx)
  306. const itemRef = shallowRef(item)
  307. // avoid creating refs if the render fn doesn't need it
  308. const keyRef = needKey ? shallowRef(key) : undefined
  309. const indexRef = needIndex ? shallowRef(index) : undefined
  310. let nodes: Block
  311. let scope: EffectScope | undefined
  312. if (isComponent) {
  313. // component already has its own scope so no outer scope needed
  314. nodes = renderItem(itemRef, keyRef as any, indexRef as any)
  315. } else {
  316. scope = new EffectScope()
  317. nodes = scope.run(() =>
  318. renderItem(itemRef, keyRef as any, indexRef as any),
  319. )!
  320. }
  321. const block = (newBlocks[idx] = new ForBlock(
  322. nodes,
  323. scope,
  324. itemRef,
  325. keyRef,
  326. indexRef,
  327. getKey && getKey(item, key, index),
  328. ))
  329. if (parent) insert(block.nodes, parent, anchor)
  330. return block
  331. }
  332. const tryPatchIndex = (source: any, idx: number) => {
  333. const block = oldBlocks[idx]
  334. const [item, key, index] = getItem(source, idx)
  335. if (block.key === getKey!(item, key, index)) {
  336. update((newBlocks[idx] = block), item)
  337. return true
  338. }
  339. }
  340. const update = (
  341. { itemRef, keyRef, indexRef }: ForBlock,
  342. newItem: any,
  343. newKey?: any,
  344. newIndex?: any,
  345. ) => {
  346. if (newItem !== itemRef.value) {
  347. itemRef.value = newItem
  348. }
  349. if (keyRef && newKey !== undefined && newKey !== keyRef.value) {
  350. keyRef.value = newKey
  351. }
  352. if (indexRef && newIndex !== undefined && newIndex !== indexRef.value) {
  353. indexRef.value = newIndex
  354. }
  355. }
  356. const unmount = ({ nodes, scope }: ForBlock, doRemove = true) => {
  357. scope && scope.stop()
  358. doRemove && removeBlock(nodes, parent!)
  359. }
  360. if (flags & VaporVForFlags.ONCE) {
  361. renderList()
  362. } else {
  363. renderEffect(renderList)
  364. }
  365. if (!isHydrating && _insertionParent) {
  366. insert(frag, _insertionParent, _insertionAnchor)
  367. }
  368. return frag
  369. }
  370. export function createForSlots(
  371. rawSource: Source,
  372. getSlot: (item: any, key: any, index?: number) => DynamicSlot,
  373. ): DynamicSlot[] {
  374. const source = normalizeSource(rawSource)
  375. const sourceLength = source.values.length
  376. const slots = new Array<DynamicSlot>(sourceLength)
  377. for (let i = 0; i < sourceLength; i++) {
  378. slots[i] = getSlot(...getItem(source, i))
  379. }
  380. return slots
  381. }
  382. function normalizeSource(source: any): ResolvedSource {
  383. let values = source
  384. let needsWrap = false
  385. let keys
  386. if (isArray(source)) {
  387. if (isReactive(source)) {
  388. needsWrap = !isShallow(source)
  389. values = shallowReadArray(source)
  390. }
  391. } else if (isString(source)) {
  392. values = source.split('')
  393. } else if (typeof source === 'number') {
  394. if (__DEV__ && !Number.isInteger(source)) {
  395. warn(`The v-for range expect an integer value but got ${source}.`)
  396. }
  397. values = new Array(source)
  398. for (let i = 0; i < source; i++) values[i] = i + 1
  399. } else if (isObject(source)) {
  400. if (source[Symbol.iterator as any]) {
  401. values = Array.from(source as Iterable<any>)
  402. } else {
  403. keys = Object.keys(source)
  404. values = new Array(keys.length)
  405. for (let i = 0, l = keys.length; i < l; i++) {
  406. values[i] = source[keys[i]]
  407. }
  408. }
  409. }
  410. return { values, needsWrap, keys }
  411. }
  412. function getItem(
  413. { keys, values, needsWrap }: ResolvedSource,
  414. idx: number,
  415. ): [item: any, key: any, index?: number] {
  416. const value = needsWrap ? toReactive(values[idx]) : values[idx]
  417. if (keys) {
  418. return [value, keys[idx], idx]
  419. } else {
  420. return [value, idx, undefined]
  421. }
  422. }
  423. function normalizeAnchor(node: Block): Node {
  424. if (node instanceof Node) {
  425. return node
  426. } else if (isArray(node)) {
  427. return normalizeAnchor(node[0])
  428. } else if (isVaporComponent(node)) {
  429. return normalizeAnchor(node.block!)
  430. } else {
  431. return normalizeAnchor(node.nodes!)
  432. }
  433. }
  434. // runtime helper for rest element destructure
  435. export function getRestElement(val: any, keys: string[]): any {
  436. const res: any = {}
  437. for (const key in val) {
  438. if (!keys.includes(key)) res[key] = val[key]
  439. }
  440. return res
  441. }
  442. export function getDefaultValue(val: any, defaultVal: any): any {
  443. return val === undefined ? defaultVal : val
  444. }