renderer.ts 49 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766
  1. import {
  2. Text,
  3. Fragment,
  4. Comment,
  5. Portal,
  6. normalizeVNode,
  7. VNode,
  8. VNodeChildren,
  9. createVNode,
  10. isSameVNodeType
  11. } from './vnode'
  12. import {
  13. ComponentInternalInstance,
  14. createComponentInstance,
  15. setupStatefulComponent,
  16. Component,
  17. Data
  18. } from './component'
  19. import {
  20. renderComponentRoot,
  21. shouldUpdateComponent,
  22. updateHOCHostEl
  23. } from './componentRenderUtils'
  24. import {
  25. isString,
  26. EMPTY_OBJ,
  27. EMPTY_ARR,
  28. isReservedProp,
  29. isFunction,
  30. PatchFlags
  31. } from '@vue/shared'
  32. import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
  33. import {
  34. effect,
  35. stop,
  36. ReactiveEffectOptions,
  37. isRef,
  38. Ref,
  39. toRaw,
  40. DebuggerEvent
  41. } from '@vue/reactivity'
  42. import { resolveProps } from './componentProps'
  43. import { resolveSlots } from './componentSlots'
  44. import { ShapeFlags } from './shapeFlags'
  45. import { pushWarningContext, popWarningContext, warn } from './warning'
  46. import { invokeDirectiveHook } from './directives'
  47. import { ComponentPublicInstance } from './componentProxy'
  48. import { App, createAppAPI } from './apiApp'
  49. import {
  50. SuspenseBoundary,
  51. queueEffectWithSuspense,
  52. SuspenseImpl
  53. } from './components/Suspense'
  54. import { ErrorCodes, callWithErrorHandling } from './errorHandling'
  55. import { KeepAliveSink, isKeepAlive } from './components/KeepAlive'
  56. import { registerHMR, unregisterHMR } from './hmr'
  57. const __HMR__ = __BUNDLER__ && __DEV__
  58. export interface RendererOptions<HostNode = any, HostElement = any> {
  59. patchProp(
  60. el: HostElement,
  61. key: string,
  62. value: any,
  63. oldValue: any,
  64. isSVG: boolean,
  65. prevChildren?: VNode<HostNode, HostElement>[],
  66. parentComponent?: ComponentInternalInstance | null,
  67. parentSuspense?: SuspenseBoundary<HostNode, HostElement> | null,
  68. unmountChildren?: (
  69. children: VNode<HostNode, HostElement>[],
  70. parentComponent: ComponentInternalInstance | null,
  71. parentSuspense: SuspenseBoundary<HostNode, HostElement> | null
  72. ) => void
  73. ): void
  74. insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void
  75. remove(el: HostNode): void
  76. createElement(type: string, isSVG?: boolean): HostElement
  77. createText(text: string): HostNode
  78. createComment(text: string): HostNode
  79. setText(node: HostNode, text: string): void
  80. setElementText(node: HostElement, text: string): void
  81. parentNode(node: HostNode): HostElement | null
  82. nextSibling(node: HostNode): HostNode | null
  83. querySelector(selector: string): HostElement | null
  84. }
  85. export type RootRenderFunction<HostNode, HostElement> = (
  86. vnode: VNode<HostNode, HostElement> | null,
  87. dom: HostElement
  88. ) => void
  89. // An object exposing the internals of a renderer, passed to tree-shakeable
  90. // features so that they can be decoupled from this file.
  91. export interface RendererInternals<HostNode = any, HostElement = any> {
  92. patch: (
  93. n1: VNode<HostNode, HostElement> | null, // null means this is a mount
  94. n2: VNode<HostNode, HostElement>,
  95. container: HostElement,
  96. anchor?: HostNode | null,
  97. parentComponent?: ComponentInternalInstance | null,
  98. parentSuspense?: SuspenseBoundary<HostNode, HostElement> | null,
  99. isSVG?: boolean,
  100. optimized?: boolean
  101. ) => void
  102. unmount: (
  103. vnode: VNode<HostNode, HostElement>,
  104. parentComponent: ComponentInternalInstance | null,
  105. parentSuspense: SuspenseBoundary<HostNode, HostElement> | null,
  106. doRemove?: boolean
  107. ) => void
  108. move: (
  109. vnode: VNode<HostNode, HostElement>,
  110. container: HostElement,
  111. anchor: HostNode | null,
  112. type: MoveType,
  113. parentSuspense?: SuspenseBoundary<HostNode, HostElement> | null
  114. ) => void
  115. next: (vnode: VNode<HostNode, HostElement>) => HostNode | null
  116. options: RendererOptions<HostNode, HostElement>
  117. }
  118. export const enum MoveType {
  119. ENTER,
  120. LEAVE,
  121. REORDER
  122. }
  123. const prodEffectOptions = {
  124. scheduler: queueJob
  125. }
  126. function createDevEffectOptions(
  127. instance: ComponentInternalInstance
  128. ): ReactiveEffectOptions {
  129. return {
  130. scheduler: queueJob,
  131. onTrack: instance.rtc ? e => invokeHooks(instance.rtc!, e) : void 0,
  132. onTrigger: instance.rtg ? e => invokeHooks(instance.rtg!, e) : void 0
  133. }
  134. }
  135. export function invokeHooks(hooks: Function[], arg?: DebuggerEvent) {
  136. for (let i = 0; i < hooks.length; i++) {
  137. hooks[i](arg)
  138. }
  139. }
  140. export const queuePostRenderEffect = __FEATURE_SUSPENSE__
  141. ? queueEffectWithSuspense
  142. : queuePostFlushCb
  143. /**
  144. * The createRenderer function accepts two generic arguments:
  145. * HostNode and HostElement, corresponding to Node and Element types in the
  146. * host environment. For example, for runtime-dom, HostNode would be the DOM
  147. * `Node` interface and HostElement would be the DOM `Element` interface.
  148. *
  149. * Custom renderers can pass in the platform specific types like this:
  150. *
  151. * ``` js
  152. * const { render, createApp } = createRenderer<Node, Element>({
  153. * patchProp,
  154. * ...nodeOps
  155. * })
  156. * ```
  157. */
  158. export function createRenderer<
  159. HostNode extends object = any,
  160. HostElement extends HostNode = any
  161. >(
  162. options: RendererOptions<HostNode, HostElement>
  163. ): {
  164. render: RootRenderFunction<HostNode, HostElement>
  165. createApp: () => App<HostElement>
  166. } {
  167. type HostVNode = VNode<HostNode, HostElement>
  168. type HostVNodeChildren = VNodeChildren<HostNode, HostElement>
  169. type HostSuspenseBoundary = SuspenseBoundary<HostNode, HostElement>
  170. const {
  171. insert: hostInsert,
  172. remove: hostRemove,
  173. patchProp: hostPatchProp,
  174. createElement: hostCreateElement,
  175. createText: hostCreateText,
  176. createComment: hostCreateComment,
  177. setText: hostSetText,
  178. setElementText: hostSetElementText,
  179. parentNode: hostParentNode,
  180. nextSibling: hostNextSibling,
  181. querySelector: hostQuerySelector
  182. } = options
  183. const internals: RendererInternals<HostNode, HostElement> = {
  184. patch,
  185. unmount,
  186. move,
  187. next: getNextHostNode,
  188. options
  189. }
  190. function patch(
  191. n1: HostVNode | null, // null means this is a mount
  192. n2: HostVNode,
  193. container: HostElement,
  194. anchor: HostNode | null = null,
  195. parentComponent: ComponentInternalInstance | null = null,
  196. parentSuspense: HostSuspenseBoundary | null = null,
  197. isSVG: boolean = false,
  198. optimized: boolean = false
  199. ) {
  200. // patching & not same type, unmount old tree
  201. if (n1 != null && !isSameVNodeType(n1, n2)) {
  202. anchor = getNextHostNode(n1)
  203. unmount(n1, parentComponent, parentSuspense, true)
  204. n1 = null
  205. }
  206. const { type, shapeFlag } = n2
  207. switch (type) {
  208. case Text:
  209. processText(n1, n2, container, anchor)
  210. break
  211. case Comment:
  212. processCommentNode(n1, n2, container, anchor)
  213. break
  214. case Fragment:
  215. processFragment(
  216. n1,
  217. n2,
  218. container,
  219. anchor,
  220. parentComponent,
  221. parentSuspense,
  222. isSVG,
  223. optimized
  224. )
  225. break
  226. case Portal:
  227. processPortal(
  228. n1,
  229. n2,
  230. container,
  231. anchor,
  232. parentComponent,
  233. parentSuspense,
  234. isSVG,
  235. optimized
  236. )
  237. break
  238. default:
  239. if (shapeFlag & ShapeFlags.ELEMENT) {
  240. processElement(
  241. n1,
  242. n2,
  243. container,
  244. anchor,
  245. parentComponent,
  246. parentSuspense,
  247. isSVG,
  248. optimized
  249. )
  250. } else if (shapeFlag & ShapeFlags.COMPONENT) {
  251. processComponent(
  252. n1,
  253. n2,
  254. container,
  255. anchor,
  256. parentComponent,
  257. parentSuspense,
  258. isSVG,
  259. optimized
  260. )
  261. } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
  262. ;(type as typeof SuspenseImpl).process(
  263. n1,
  264. n2,
  265. container,
  266. anchor,
  267. parentComponent,
  268. parentSuspense,
  269. isSVG,
  270. optimized,
  271. internals
  272. )
  273. } else if (__DEV__) {
  274. warn('Invalid HostVNode type:', n2.type, `(${typeof n2.type})`)
  275. }
  276. }
  277. }
  278. function processText(
  279. n1: HostVNode | null,
  280. n2: HostVNode,
  281. container: HostElement,
  282. anchor: HostNode | null
  283. ) {
  284. if (n1 == null) {
  285. hostInsert(
  286. (n2.el = hostCreateText(n2.children as string)),
  287. container,
  288. anchor
  289. )
  290. } else {
  291. const el = (n2.el = n1.el) as HostNode
  292. if (n2.children !== n1.children) {
  293. hostSetText(el, n2.children as string)
  294. }
  295. }
  296. }
  297. function processCommentNode(
  298. n1: HostVNode | null,
  299. n2: HostVNode,
  300. container: HostElement,
  301. anchor: HostNode | null
  302. ) {
  303. if (n1 == null) {
  304. hostInsert(
  305. (n2.el = hostCreateComment((n2.children as string) || '')),
  306. container,
  307. anchor
  308. )
  309. } else {
  310. // there's no support for dynamic comments
  311. n2.el = n1.el
  312. }
  313. }
  314. function processElement(
  315. n1: HostVNode | null,
  316. n2: HostVNode,
  317. container: HostElement,
  318. anchor: HostNode | null,
  319. parentComponent: ComponentInternalInstance | null,
  320. parentSuspense: HostSuspenseBoundary | null,
  321. isSVG: boolean,
  322. optimized: boolean
  323. ) {
  324. if (n1 == null) {
  325. mountElement(
  326. n2,
  327. container,
  328. anchor,
  329. parentComponent,
  330. parentSuspense,
  331. isSVG,
  332. optimized
  333. )
  334. } else {
  335. patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized)
  336. }
  337. if (n2.ref !== null && parentComponent !== null) {
  338. setRef(n2.ref, n1 && n1.ref, parentComponent, n2.el)
  339. }
  340. }
  341. function mountElement(
  342. vnode: HostVNode,
  343. container: HostElement,
  344. anchor: HostNode | null,
  345. parentComponent: ComponentInternalInstance | null,
  346. parentSuspense: HostSuspenseBoundary | null,
  347. isSVG: boolean,
  348. optimized: boolean
  349. ) {
  350. const tag = vnode.type as string
  351. isSVG = isSVG || tag === 'svg'
  352. const el = (vnode.el = hostCreateElement(tag, isSVG))
  353. const { props, shapeFlag, transition } = vnode
  354. if (props != null) {
  355. for (const key in props) {
  356. if (isReservedProp(key)) continue
  357. hostPatchProp(el, key, props[key], null, isSVG)
  358. }
  359. if (props.onVnodeBeforeMount != null) {
  360. invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode)
  361. }
  362. }
  363. if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
  364. hostSetElementText(el, vnode.children as string)
  365. } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  366. mountChildren(
  367. vnode.children as HostVNodeChildren,
  368. el,
  369. null,
  370. parentComponent,
  371. parentSuspense,
  372. isSVG,
  373. optimized || vnode.dynamicChildren !== null
  374. )
  375. }
  376. if (transition != null && !transition.persisted) {
  377. transition.beforeEnter(el)
  378. }
  379. hostInsert(el, container, anchor)
  380. const vnodeMountedHook = props && props.onVnodeMounted
  381. if (
  382. vnodeMountedHook != null ||
  383. (transition != null && !transition.persisted)
  384. ) {
  385. queuePostRenderEffect(() => {
  386. vnodeMountedHook &&
  387. invokeDirectiveHook(vnodeMountedHook, parentComponent, vnode)
  388. transition && !transition.persisted && transition.enter(el)
  389. }, parentSuspense)
  390. }
  391. }
  392. function mountChildren(
  393. children: HostVNodeChildren,
  394. container: HostElement,
  395. anchor: HostNode | null,
  396. parentComponent: ComponentInternalInstance | null,
  397. parentSuspense: HostSuspenseBoundary | null,
  398. isSVG: boolean,
  399. optimized: boolean,
  400. start: number = 0
  401. ) {
  402. for (let i = start; i < children.length; i++) {
  403. const child = optimized
  404. ? (children[i] as HostVNode)
  405. : (children[i] = normalizeVNode(children[i]))
  406. patch(
  407. null,
  408. child,
  409. container,
  410. anchor,
  411. parentComponent,
  412. parentSuspense,
  413. isSVG,
  414. optimized
  415. )
  416. }
  417. }
  418. function patchElement(
  419. n1: HostVNode,
  420. n2: HostVNode,
  421. parentComponent: ComponentInternalInstance | null,
  422. parentSuspense: HostSuspenseBoundary | null,
  423. isSVG: boolean,
  424. optimized: boolean
  425. ) {
  426. const el = (n2.el = n1.el) as HostElement
  427. let { patchFlag, dynamicChildren } = n2
  428. const oldProps = (n1 && n1.props) || EMPTY_OBJ
  429. const newProps = n2.props || EMPTY_OBJ
  430. if (newProps.onVnodeBeforeUpdate != null) {
  431. invokeDirectiveHook(newProps.onVnodeBeforeUpdate, parentComponent, n2, n1)
  432. }
  433. if (__HMR__ && parentComponent && parentComponent.renderUpdated) {
  434. // HMR updated, force full diff
  435. patchFlag = 0
  436. optimized = false
  437. }
  438. if (patchFlag > 0) {
  439. // the presence of a patchFlag means this element's render code was
  440. // generated by the compiler and can take the fast path.
  441. // in this path old node and new node are guaranteed to have the same shape
  442. // (i.e. at the exact same position in the source template)
  443. if (patchFlag & PatchFlags.FULL_PROPS) {
  444. // element props contain dynamic keys, full diff needed
  445. patchProps(
  446. el,
  447. n2,
  448. oldProps,
  449. newProps,
  450. parentComponent,
  451. parentSuspense,
  452. isSVG
  453. )
  454. } else {
  455. // class
  456. // this flag is matched when the element has dynamic class bindings.
  457. if (patchFlag & PatchFlags.CLASS) {
  458. if (oldProps.class !== newProps.class) {
  459. hostPatchProp(el, 'class', newProps.class, null, isSVG)
  460. }
  461. }
  462. // style
  463. // this flag is matched when the element has dynamic style bindings
  464. if (patchFlag & PatchFlags.STYLE) {
  465. hostPatchProp(el, 'style', newProps.style, oldProps.style, isSVG)
  466. }
  467. // props
  468. // This flag is matched when the element has dynamic prop/attr bindings
  469. // other than class and style. The keys of dynamic prop/attrs are saved for
  470. // faster iteration.
  471. // Note dynamic keys like :[foo]="bar" will cause this optimization to
  472. // bail out and go through a full diff because we need to unset the old key
  473. if (patchFlag & PatchFlags.PROPS) {
  474. // if the flag is present then dynamicProps must be non-null
  475. const propsToUpdate = n2.dynamicProps!
  476. for (let i = 0; i < propsToUpdate.length; i++) {
  477. const key = propsToUpdate[i]
  478. const prev = oldProps[key]
  479. const next = newProps[key]
  480. if (prev !== next) {
  481. hostPatchProp(
  482. el,
  483. key,
  484. next,
  485. prev,
  486. isSVG,
  487. n1.children as HostVNode[],
  488. parentComponent,
  489. parentSuspense,
  490. unmountChildren
  491. )
  492. }
  493. }
  494. }
  495. }
  496. // text
  497. // This flag is matched when the element has only dynamic text children.
  498. // this flag is terminal (i.e. skips children diffing).
  499. if (patchFlag & PatchFlags.TEXT) {
  500. if (n1.children !== n2.children) {
  501. hostSetElementText(el, n2.children as string)
  502. }
  503. return // terminal
  504. }
  505. } else if (!optimized && dynamicChildren == null) {
  506. // unoptimized, full diff
  507. patchProps(
  508. el,
  509. n2,
  510. oldProps,
  511. newProps,
  512. parentComponent,
  513. parentSuspense,
  514. isSVG
  515. )
  516. }
  517. if (dynamicChildren != null) {
  518. patchBlockChildren(
  519. n1.dynamicChildren!,
  520. dynamicChildren,
  521. el,
  522. parentComponent,
  523. parentSuspense,
  524. isSVG
  525. )
  526. } else if (!optimized) {
  527. // full diff
  528. patchChildren(n1, n2, el, null, parentComponent, parentSuspense, isSVG)
  529. }
  530. if (newProps.onVnodeUpdated != null) {
  531. queuePostRenderEffect(() => {
  532. invokeDirectiveHook(newProps.onVnodeUpdated, parentComponent, n2, n1)
  533. }, parentSuspense)
  534. }
  535. }
  536. // The fast path for blocks.
  537. function patchBlockChildren(
  538. oldChildren: HostVNode[],
  539. newChildren: HostVNode[],
  540. fallbackContainer: HostElement,
  541. parentComponent: ComponentInternalInstance | null,
  542. parentSuspense: HostSuspenseBoundary | null,
  543. isSVG: boolean
  544. ) {
  545. for (let i = 0; i < newChildren.length; i++) {
  546. const oldVNode = oldChildren[i]
  547. patch(
  548. oldVNode,
  549. newChildren[i],
  550. // - In the case of a Fragment, we need to provide the actual parent
  551. // of the Fragment itself so it can move its children.
  552. // - In the case of a Comment, this is likely a v-if toggle, which also
  553. // needs the correct parent container.
  554. // In other cases, the parent container is not actually used so we just
  555. // pass the block element here to avoid a DOM parentNode call.
  556. oldVNode.type === Fragment || oldVNode.type === Comment
  557. ? hostParentNode(oldVNode.el!)!
  558. : fallbackContainer,
  559. null,
  560. parentComponent,
  561. parentSuspense,
  562. isSVG,
  563. true
  564. )
  565. }
  566. }
  567. function patchProps(
  568. el: HostElement,
  569. vnode: HostVNode,
  570. oldProps: Data,
  571. newProps: Data,
  572. parentComponent: ComponentInternalInstance | null,
  573. parentSuspense: HostSuspenseBoundary | null,
  574. isSVG: boolean
  575. ) {
  576. if (oldProps !== newProps) {
  577. for (const key in newProps) {
  578. if (isReservedProp(key)) continue
  579. const next = newProps[key]
  580. const prev = oldProps[key]
  581. if (next !== prev) {
  582. hostPatchProp(
  583. el,
  584. key,
  585. next,
  586. prev,
  587. isSVG,
  588. vnode.children as HostVNode[],
  589. parentComponent,
  590. parentSuspense,
  591. unmountChildren
  592. )
  593. }
  594. }
  595. if (oldProps !== EMPTY_OBJ) {
  596. for (const key in oldProps) {
  597. if (!isReservedProp(key) && !(key in newProps)) {
  598. hostPatchProp(
  599. el,
  600. key,
  601. null,
  602. null,
  603. isSVG,
  604. vnode.children as HostVNode[],
  605. parentComponent,
  606. parentSuspense,
  607. unmountChildren
  608. )
  609. }
  610. }
  611. }
  612. }
  613. }
  614. let devFragmentID = 0
  615. function processFragment(
  616. n1: HostVNode | null,
  617. n2: HostVNode,
  618. container: HostElement,
  619. anchor: HostNode | null,
  620. parentComponent: ComponentInternalInstance | null,
  621. parentSuspense: HostSuspenseBoundary | null,
  622. isSVG: boolean,
  623. optimized: boolean
  624. ) {
  625. const showID = __DEV__ && !__TEST__
  626. const fragmentStartAnchor = (n2.el = n1
  627. ? n1.el
  628. : hostCreateComment(showID ? `fragment-${devFragmentID}-start` : ''))!
  629. const fragmentEndAnchor = (n2.anchor = n1
  630. ? n1.anchor
  631. : hostCreateComment(showID ? `fragment-${devFragmentID}-end` : ''))!
  632. let { patchFlag } = n2
  633. if (patchFlag > 0) {
  634. optimized = true
  635. }
  636. if (__HMR__ && parentComponent && parentComponent.renderUpdated) {
  637. // HMR updated, force full diff
  638. patchFlag = 0
  639. optimized = false
  640. }
  641. if (n1 == null) {
  642. if (showID) {
  643. devFragmentID++
  644. }
  645. hostInsert(fragmentStartAnchor, container, anchor)
  646. hostInsert(fragmentEndAnchor, container, anchor)
  647. // a fragment can only have array children
  648. // since they are either generated by the compiler, or implicitly created
  649. // from arrays.
  650. mountChildren(
  651. n2.children as HostVNodeChildren,
  652. container,
  653. fragmentEndAnchor,
  654. parentComponent,
  655. parentSuspense,
  656. isSVG,
  657. optimized
  658. )
  659. } else {
  660. if (patchFlag & PatchFlags.STABLE_FRAGMENT && n2.dynamicChildren) {
  661. // a stable fragment (template root or <template v-for>) doesn't need to
  662. // patch children order, but it may contain dynamicChildren.
  663. patchBlockChildren(
  664. n1.dynamicChildren!,
  665. n2.dynamicChildren,
  666. container,
  667. parentComponent,
  668. parentSuspense,
  669. isSVG
  670. )
  671. } else {
  672. // keyed / unkeyed, or manual fragments.
  673. // for keyed & unkeyed, since they are compiler generated from v-for,
  674. // each child is guaranteed to be a block so the fragment will never
  675. // have dynamicChildren.
  676. patchChildren(
  677. n1,
  678. n2,
  679. container,
  680. fragmentEndAnchor,
  681. parentComponent,
  682. parentSuspense,
  683. isSVG,
  684. optimized
  685. )
  686. }
  687. }
  688. }
  689. function processPortal(
  690. n1: HostVNode | null,
  691. n2: HostVNode,
  692. container: HostElement,
  693. anchor: HostNode | null,
  694. parentComponent: ComponentInternalInstance | null,
  695. parentSuspense: HostSuspenseBoundary | null,
  696. isSVG: boolean,
  697. optimized: boolean
  698. ) {
  699. const targetSelector = n2.props && n2.props.target
  700. const { patchFlag, shapeFlag, children } = n2
  701. if (n1 == null) {
  702. const target = (n2.target = isString(targetSelector)
  703. ? hostQuerySelector(targetSelector)
  704. : targetSelector)
  705. if (target != null) {
  706. if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
  707. hostSetElementText(target, children as string)
  708. } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  709. mountChildren(
  710. children as HostVNodeChildren,
  711. target,
  712. null,
  713. parentComponent,
  714. parentSuspense,
  715. isSVG,
  716. optimized
  717. )
  718. }
  719. } else if (__DEV__) {
  720. warn('Invalid Portal target on mount:', target, `(${typeof target})`)
  721. }
  722. } else {
  723. // update content
  724. const target = (n2.target = n1.target)!
  725. if (patchFlag === PatchFlags.TEXT) {
  726. hostSetElementText(target, children as string)
  727. } else if (n2.dynamicChildren) {
  728. // fast path when the portal happens to be a block root
  729. patchBlockChildren(
  730. n1.dynamicChildren!,
  731. n2.dynamicChildren,
  732. container,
  733. parentComponent,
  734. parentSuspense,
  735. isSVG
  736. )
  737. } else if (!optimized) {
  738. patchChildren(
  739. n1,
  740. n2,
  741. target,
  742. null,
  743. parentComponent,
  744. parentSuspense,
  745. isSVG
  746. )
  747. }
  748. // target changed
  749. if (targetSelector !== (n1.props && n1.props.target)) {
  750. const nextTarget = (n2.target = isString(targetSelector)
  751. ? hostQuerySelector(targetSelector)
  752. : targetSelector)
  753. if (nextTarget != null) {
  754. // move content
  755. if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
  756. hostSetElementText(target, '')
  757. hostSetElementText(nextTarget, children as string)
  758. } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  759. for (let i = 0; i < (children as HostVNode[]).length; i++) {
  760. move(
  761. (children as HostVNode[])[i],
  762. nextTarget,
  763. null,
  764. MoveType.REORDER
  765. )
  766. }
  767. }
  768. } else if (__DEV__) {
  769. warn('Invalid Portal target on update:', target, `(${typeof target})`)
  770. }
  771. }
  772. }
  773. // insert an empty node as the placeholder for the portal
  774. processCommentNode(n1, n2, container, anchor)
  775. }
  776. function processComponent(
  777. n1: HostVNode | null,
  778. n2: HostVNode,
  779. container: HostElement,
  780. anchor: HostNode | null,
  781. parentComponent: ComponentInternalInstance | null,
  782. parentSuspense: HostSuspenseBoundary | null,
  783. isSVG: boolean,
  784. optimized: boolean
  785. ) {
  786. if (n1 == null) {
  787. if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
  788. ;(parentComponent!.sink as KeepAliveSink).activate(
  789. n2,
  790. container,
  791. anchor
  792. )
  793. } else {
  794. mountComponent(
  795. n2,
  796. container,
  797. anchor,
  798. parentComponent,
  799. parentSuspense,
  800. isSVG
  801. )
  802. }
  803. } else {
  804. const instance = (n2.component = n1.component)!
  805. if (shouldUpdateComponent(n1, n2, parentComponent, optimized)) {
  806. if (
  807. __FEATURE_SUSPENSE__ &&
  808. instance.asyncDep &&
  809. !instance.asyncResolved
  810. ) {
  811. // async & still pending - just update props and slots
  812. // since the component's reactive effect for render isn't set-up yet
  813. if (__DEV__) {
  814. pushWarningContext(n2)
  815. }
  816. updateComponentPreRender(instance, n2)
  817. if (__DEV__) {
  818. popWarningContext()
  819. }
  820. return
  821. } else {
  822. // normal update
  823. instance.next = n2
  824. // instance.update is the reactive effect runner.
  825. instance.update()
  826. }
  827. } else {
  828. // no update needed. just copy over properties
  829. n2.component = n1.component
  830. n2.el = n1.el
  831. }
  832. }
  833. if (n2.ref !== null && parentComponent !== null) {
  834. if (__DEV__ && !(n2.shapeFlag & ShapeFlags.STATEFUL_COMPONENT)) {
  835. pushWarningContext(n2)
  836. warn(
  837. `Functional components do not support "ref" because they do not ` +
  838. `have instances.`
  839. )
  840. popWarningContext()
  841. }
  842. setRef(n2.ref, n1 && n1.ref, parentComponent, n2.component!.proxy)
  843. }
  844. }
  845. function mountComponent(
  846. initialVNode: HostVNode,
  847. container: HostElement,
  848. anchor: HostNode | null,
  849. parentComponent: ComponentInternalInstance | null,
  850. parentSuspense: HostSuspenseBoundary | null,
  851. isSVG: boolean
  852. ) {
  853. const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
  854. initialVNode,
  855. parentComponent
  856. ))
  857. if (__HMR__ && instance.type.__hmrId != null) {
  858. registerHMR(instance)
  859. }
  860. if (__DEV__) {
  861. pushWarningContext(initialVNode)
  862. }
  863. const Comp = initialVNode.type as Component
  864. // inject renderer internals for keepAlive
  865. if (isKeepAlive(initialVNode)) {
  866. const sink = instance.sink as KeepAliveSink
  867. sink.renderer = internals
  868. sink.parentSuspense = parentSuspense
  869. }
  870. // resolve props and slots for setup context
  871. const propsOptions = Comp.props
  872. resolveProps(instance, initialVNode.props, propsOptions)
  873. resolveSlots(instance, initialVNode.children)
  874. // setup stateful logic
  875. if (initialVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
  876. setupStatefulComponent(instance, parentSuspense)
  877. }
  878. // setup() is async. This component relies on async logic to be resolved
  879. // before proceeding
  880. if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
  881. if (!parentSuspense) {
  882. if (__DEV__) warn('async setup() is used without a suspense boundary!')
  883. return
  884. }
  885. parentSuspense.registerDep(instance, setupRenderEffect)
  886. // give it a placeholder
  887. const placeholder = (instance.subTree = createVNode(Comment))
  888. processCommentNode(null, placeholder, container, anchor)
  889. initialVNode.el = placeholder.el
  890. return
  891. }
  892. setupRenderEffect(
  893. instance,
  894. parentSuspense,
  895. initialVNode,
  896. container,
  897. anchor,
  898. isSVG
  899. )
  900. if (__DEV__) {
  901. popWarningContext()
  902. }
  903. }
  904. function setupRenderEffect(
  905. instance: ComponentInternalInstance,
  906. parentSuspense: HostSuspenseBoundary | null,
  907. initialVNode: HostVNode,
  908. container: HostElement,
  909. anchor: HostNode | null,
  910. isSVG: boolean
  911. ) {
  912. // create reactive effect for rendering
  913. instance.update = effect(function componentEffect() {
  914. if (!instance.isMounted) {
  915. const subTree = (instance.subTree = renderComponentRoot(instance))
  916. // beforeMount hook
  917. if (instance.bm !== null) {
  918. invokeHooks(instance.bm)
  919. }
  920. patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)
  921. initialVNode.el = subTree.el
  922. // mounted hook
  923. if (instance.m !== null) {
  924. queuePostRenderEffect(instance.m, parentSuspense)
  925. }
  926. // activated hook for keep-alive roots.
  927. if (
  928. instance.a !== null &&
  929. instance.vnode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
  930. ) {
  931. queuePostRenderEffect(instance.a, parentSuspense)
  932. }
  933. instance.isMounted = true
  934. } else {
  935. // updateComponent
  936. // This is triggered by mutation of component's own state (next: null)
  937. // OR parent calling processComponent (next: HostVNode)
  938. const { next } = instance
  939. if (__DEV__) {
  940. pushWarningContext(next || instance.vnode)
  941. }
  942. if (next !== null) {
  943. updateComponentPreRender(instance, next)
  944. }
  945. const nextTree = renderComponentRoot(instance)
  946. const prevTree = instance.subTree
  947. instance.subTree = nextTree
  948. // beforeUpdate hook
  949. if (instance.bu !== null) {
  950. invokeHooks(instance.bu)
  951. }
  952. // reset refs
  953. // only needed if previous patch had refs
  954. if (instance.refs !== EMPTY_OBJ) {
  955. instance.refs = {}
  956. }
  957. patch(
  958. prevTree,
  959. nextTree,
  960. // parent may have changed if it's in a portal
  961. hostParentNode(prevTree.el as HostNode) as HostElement,
  962. // anchor may have changed if it's in a fragment
  963. getNextHostNode(prevTree),
  964. instance,
  965. parentSuspense,
  966. isSVG
  967. )
  968. instance.vnode.el = nextTree.el
  969. if (next === null) {
  970. // self-triggered update. In case of HOC, update parent component
  971. // vnode el. HOC is indicated by parent instance's subTree pointing
  972. // to child component's vnode
  973. updateHOCHostEl(instance, nextTree.el)
  974. }
  975. // updated hook
  976. if (instance.u !== null) {
  977. queuePostRenderEffect(instance.u, parentSuspense)
  978. }
  979. if (__DEV__) {
  980. popWarningContext()
  981. }
  982. }
  983. }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
  984. }
  985. function updateComponentPreRender(
  986. instance: ComponentInternalInstance,
  987. nextVNode: HostVNode
  988. ) {
  989. nextVNode.component = instance
  990. instance.vnode = nextVNode
  991. instance.next = null
  992. resolveProps(instance, nextVNode.props, (nextVNode.type as Component).props)
  993. resolveSlots(instance, nextVNode.children)
  994. }
  995. function patchChildren(
  996. n1: HostVNode | null,
  997. n2: HostVNode,
  998. container: HostElement,
  999. anchor: HostNode | null,
  1000. parentComponent: ComponentInternalInstance | null,
  1001. parentSuspense: HostSuspenseBoundary | null,
  1002. isSVG: boolean,
  1003. optimized: boolean = false
  1004. ) {
  1005. const c1 = n1 && n1.children
  1006. const prevShapeFlag = n1 ? n1.shapeFlag : 0
  1007. const c2 = n2.children
  1008. const { patchFlag, shapeFlag } = n2
  1009. if (patchFlag === PatchFlags.BAIL) {
  1010. optimized = false
  1011. }
  1012. // fast path
  1013. if (patchFlag > 0) {
  1014. if (patchFlag & PatchFlags.KEYED_FRAGMENT) {
  1015. // this could be either fully-keyed or mixed (some keyed some not)
  1016. // presence of patchFlag means children are guaranteed to be arrays
  1017. patchKeyedChildren(
  1018. c1 as HostVNode[],
  1019. c2 as HostVNodeChildren,
  1020. container,
  1021. anchor,
  1022. parentComponent,
  1023. parentSuspense,
  1024. isSVG,
  1025. optimized
  1026. )
  1027. return
  1028. } else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {
  1029. // unkeyed
  1030. patchUnkeyedChildren(
  1031. c1 as HostVNode[],
  1032. c2 as HostVNodeChildren,
  1033. container,
  1034. anchor,
  1035. parentComponent,
  1036. parentSuspense,
  1037. isSVG,
  1038. optimized
  1039. )
  1040. return
  1041. }
  1042. }
  1043. // children has 3 possibilities: text, array or no children.
  1044. if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
  1045. // text children fast path
  1046. if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  1047. unmountChildren(c1 as HostVNode[], parentComponent, parentSuspense)
  1048. }
  1049. if (c2 !== c1) {
  1050. hostSetElementText(container, c2 as string)
  1051. }
  1052. } else {
  1053. if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  1054. // prev children was array
  1055. if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  1056. // two arrays, cannot assume anything, do full diff
  1057. patchKeyedChildren(
  1058. c1 as HostVNode[],
  1059. c2 as HostVNodeChildren,
  1060. container,
  1061. anchor,
  1062. parentComponent,
  1063. parentSuspense,
  1064. isSVG,
  1065. optimized
  1066. )
  1067. } else {
  1068. // no new children, just unmount old
  1069. unmountChildren(
  1070. c1 as HostVNode[],
  1071. parentComponent,
  1072. parentSuspense,
  1073. true
  1074. )
  1075. }
  1076. } else {
  1077. // prev children was text OR null
  1078. // new children is array OR null
  1079. if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
  1080. hostSetElementText(container, '')
  1081. }
  1082. // mount new if array
  1083. if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  1084. mountChildren(
  1085. c2 as HostVNodeChildren,
  1086. container,
  1087. anchor,
  1088. parentComponent,
  1089. parentSuspense,
  1090. isSVG,
  1091. optimized
  1092. )
  1093. }
  1094. }
  1095. }
  1096. }
  1097. function patchUnkeyedChildren(
  1098. c1: HostVNode[],
  1099. c2: HostVNodeChildren,
  1100. container: HostElement,
  1101. anchor: HostNode | null,
  1102. parentComponent: ComponentInternalInstance | null,
  1103. parentSuspense: HostSuspenseBoundary | null,
  1104. isSVG: boolean,
  1105. optimized: boolean
  1106. ) {
  1107. c1 = c1 || EMPTY_ARR
  1108. c2 = c2 || EMPTY_ARR
  1109. const oldLength = c1.length
  1110. const newLength = c2.length
  1111. const commonLength = Math.min(oldLength, newLength)
  1112. let i
  1113. for (i = 0; i < commonLength; i++) {
  1114. const nextChild = optimized
  1115. ? (c2[i] as HostVNode)
  1116. : (c2[i] = normalizeVNode(c2[i]))
  1117. patch(
  1118. c1[i],
  1119. nextChild,
  1120. container,
  1121. null,
  1122. parentComponent,
  1123. parentSuspense,
  1124. isSVG,
  1125. optimized
  1126. )
  1127. }
  1128. if (oldLength > newLength) {
  1129. // remove old
  1130. unmountChildren(c1, parentComponent, parentSuspense, true, commonLength)
  1131. } else {
  1132. // mount new
  1133. mountChildren(
  1134. c2,
  1135. container,
  1136. anchor,
  1137. parentComponent,
  1138. parentSuspense,
  1139. isSVG,
  1140. optimized,
  1141. commonLength
  1142. )
  1143. }
  1144. }
  1145. // can be all-keyed or mixed
  1146. function patchKeyedChildren(
  1147. c1: HostVNode[],
  1148. c2: HostVNodeChildren,
  1149. container: HostElement,
  1150. parentAnchor: HostNode | null,
  1151. parentComponent: ComponentInternalInstance | null,
  1152. parentSuspense: HostSuspenseBoundary | null,
  1153. isSVG: boolean,
  1154. optimized: boolean
  1155. ) {
  1156. let i = 0
  1157. const l2 = c2.length
  1158. let e1 = c1.length - 1 // prev ending index
  1159. let e2 = l2 - 1 // next ending index
  1160. // 1. sync from start
  1161. // (a b) c
  1162. // (a b) d e
  1163. while (i <= e1 && i <= e2) {
  1164. const n1 = c1[i]
  1165. const n2 = optimized
  1166. ? (c2[i] as HostVNode)
  1167. : (c2[i] = normalizeVNode(c2[i]))
  1168. if (isSameVNodeType(n1, n2)) {
  1169. patch(
  1170. n1,
  1171. n2,
  1172. container,
  1173. parentAnchor,
  1174. parentComponent,
  1175. parentSuspense,
  1176. isSVG,
  1177. optimized
  1178. )
  1179. } else {
  1180. break
  1181. }
  1182. i++
  1183. }
  1184. // 2. sync from end
  1185. // a (b c)
  1186. // d e (b c)
  1187. while (i <= e1 && i <= e2) {
  1188. const n1 = c1[e1]
  1189. const n2 = optimized
  1190. ? (c2[e2] as HostVNode)
  1191. : (c2[e2] = normalizeVNode(c2[e2]))
  1192. if (isSameVNodeType(n1, n2)) {
  1193. patch(
  1194. n1,
  1195. n2,
  1196. container,
  1197. parentAnchor,
  1198. parentComponent,
  1199. parentSuspense,
  1200. isSVG,
  1201. optimized
  1202. )
  1203. } else {
  1204. break
  1205. }
  1206. e1--
  1207. e2--
  1208. }
  1209. // 3. common sequence + mount
  1210. // (a b)
  1211. // (a b) c
  1212. // i = 2, e1 = 1, e2 = 2
  1213. // (a b)
  1214. // c (a b)
  1215. // i = 0, e1 = -1, e2 = 0
  1216. if (i > e1) {
  1217. if (i <= e2) {
  1218. const nextPos = e2 + 1
  1219. const anchor =
  1220. nextPos < l2 ? (c2[nextPos] as HostVNode).el : parentAnchor
  1221. while (i <= e2) {
  1222. patch(
  1223. null,
  1224. optimized ? (c2[i] as HostVNode) : (c2[i] = normalizeVNode(c2[i])),
  1225. container,
  1226. anchor,
  1227. parentComponent,
  1228. parentSuspense,
  1229. isSVG
  1230. )
  1231. i++
  1232. }
  1233. }
  1234. }
  1235. // 4. common sequence + unmount
  1236. // (a b) c
  1237. // (a b)
  1238. // i = 2, e1 = 2, e2 = 1
  1239. // a (b c)
  1240. // (b c)
  1241. // i = 0, e1 = 0, e2 = -1
  1242. else if (i > e2) {
  1243. while (i <= e1) {
  1244. unmount(c1[i], parentComponent, parentSuspense, true)
  1245. i++
  1246. }
  1247. }
  1248. // 5. unknown sequence
  1249. // [i ... e1 + 1]: a b [c d e] f g
  1250. // [i ... e2 + 1]: a b [e d c h] f g
  1251. // i = 2, e1 = 4, e2 = 5
  1252. else {
  1253. const s1 = i // prev starting index
  1254. const s2 = i // next starting index
  1255. // 5.1 build key:index map for newChildren
  1256. const keyToNewIndexMap: Map<string | number, number> = new Map()
  1257. for (i = s2; i <= e2; i++) {
  1258. const nextChild = optimized
  1259. ? (c2[i] as HostVNode)
  1260. : (c2[i] = normalizeVNode(c2[i]))
  1261. if (nextChild.key != null) {
  1262. if (__DEV__ && keyToNewIndexMap.has(nextChild.key)) {
  1263. warn(
  1264. `Duplicate keys found during update:`,
  1265. JSON.stringify(nextChild.key),
  1266. `Make sure keys are unique.`
  1267. )
  1268. }
  1269. keyToNewIndexMap.set(nextChild.key, i)
  1270. }
  1271. }
  1272. // 5.2 loop through old children left to be patched and try to patch
  1273. // matching nodes & remove nodes that are no longer present
  1274. let j
  1275. let patched = 0
  1276. const toBePatched = e2 - s2 + 1
  1277. let moved = false
  1278. // used to track whether any node has moved
  1279. let maxNewIndexSoFar = 0
  1280. // works as Map<newIndex, oldIndex>
  1281. // Note that oldIndex is offset by +1
  1282. // and oldIndex = 0 is a special value indicating the new node has
  1283. // no corresponding old node.
  1284. // used for determining longest stable subsequence
  1285. const newIndexToOldIndexMap = new Array(toBePatched)
  1286. for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0
  1287. for (i = s1; i <= e1; i++) {
  1288. const prevChild = c1[i]
  1289. if (patched >= toBePatched) {
  1290. // all new children have been patched so this can only be a removal
  1291. unmount(prevChild, parentComponent, parentSuspense, true)
  1292. continue
  1293. }
  1294. let newIndex
  1295. if (prevChild.key != null) {
  1296. newIndex = keyToNewIndexMap.get(prevChild.key)
  1297. } else {
  1298. // key-less node, try to locate a key-less node of the same type
  1299. for (j = s2; j <= e2; j++) {
  1300. if (
  1301. newIndexToOldIndexMap[j - s2] === 0 &&
  1302. isSameVNodeType(prevChild, c2[j] as HostVNode)
  1303. ) {
  1304. newIndex = j
  1305. break
  1306. }
  1307. }
  1308. }
  1309. if (newIndex === undefined) {
  1310. unmount(prevChild, parentComponent, parentSuspense, true)
  1311. } else {
  1312. newIndexToOldIndexMap[newIndex - s2] = i + 1
  1313. if (newIndex >= maxNewIndexSoFar) {
  1314. maxNewIndexSoFar = newIndex
  1315. } else {
  1316. moved = true
  1317. }
  1318. patch(
  1319. prevChild,
  1320. c2[newIndex] as HostVNode,
  1321. container,
  1322. null,
  1323. parentComponent,
  1324. parentSuspense,
  1325. isSVG,
  1326. optimized
  1327. )
  1328. patched++
  1329. }
  1330. }
  1331. // 5.3 move and mount
  1332. // generate longest stable subsequence only when nodes have moved
  1333. const increasingNewIndexSequence = moved
  1334. ? getSequence(newIndexToOldIndexMap)
  1335. : EMPTY_ARR
  1336. j = increasingNewIndexSequence.length - 1
  1337. // looping backwards so that we can use last patched node as anchor
  1338. for (i = toBePatched - 1; i >= 0; i--) {
  1339. const nextIndex = s2 + i
  1340. const nextChild = c2[nextIndex] as HostVNode
  1341. const anchor =
  1342. nextIndex + 1 < l2
  1343. ? (c2[nextIndex + 1] as HostVNode).el
  1344. : parentAnchor
  1345. if (newIndexToOldIndexMap[i] === 0) {
  1346. // mount new
  1347. patch(
  1348. null,
  1349. nextChild,
  1350. container,
  1351. anchor,
  1352. parentComponent,
  1353. parentSuspense,
  1354. isSVG
  1355. )
  1356. } else if (moved) {
  1357. // move if:
  1358. // There is no stable subsequence (e.g. a reverse)
  1359. // OR current node is not among the stable sequence
  1360. if (j < 0 || i !== increasingNewIndexSequence[j]) {
  1361. move(nextChild, container, anchor, MoveType.REORDER)
  1362. } else {
  1363. j--
  1364. }
  1365. }
  1366. }
  1367. }
  1368. }
  1369. function move(
  1370. vnode: HostVNode,
  1371. container: HostElement,
  1372. anchor: HostNode | null,
  1373. type: MoveType,
  1374. parentSuspense: HostSuspenseBoundary | null = null
  1375. ) {
  1376. if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
  1377. move(vnode.component!.subTree, container, anchor, type)
  1378. return
  1379. }
  1380. if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
  1381. vnode.suspense!.move(container, anchor, type)
  1382. return
  1383. }
  1384. if (vnode.type === Fragment) {
  1385. hostInsert(vnode.el!, container, anchor)
  1386. const children = vnode.children as HostVNode[]
  1387. for (let i = 0; i < children.length; i++) {
  1388. move(children[i], container, anchor, type)
  1389. }
  1390. hostInsert(vnode.anchor!, container, anchor)
  1391. } else {
  1392. // Plain element
  1393. const { el, transition, shapeFlag } = vnode
  1394. const needTransition =
  1395. type !== MoveType.REORDER &&
  1396. shapeFlag & ShapeFlags.ELEMENT &&
  1397. transition != null
  1398. if (needTransition) {
  1399. if (type === MoveType.ENTER) {
  1400. transition!.beforeEnter(el!)
  1401. hostInsert(el!, container, anchor)
  1402. queuePostRenderEffect(() => transition!.enter(el!), parentSuspense)
  1403. } else {
  1404. const { leave, delayLeave, afterLeave } = transition!
  1405. const remove = () => hostInsert(el!, container, anchor)
  1406. const performLeave = () => {
  1407. leave(el!, () => {
  1408. remove()
  1409. afterLeave && afterLeave()
  1410. })
  1411. }
  1412. if (delayLeave) {
  1413. delayLeave(el!, remove, performLeave)
  1414. } else {
  1415. performLeave()
  1416. }
  1417. }
  1418. } else {
  1419. hostInsert(el!, container, anchor)
  1420. }
  1421. }
  1422. }
  1423. function unmount(
  1424. vnode: HostVNode,
  1425. parentComponent: ComponentInternalInstance | null,
  1426. parentSuspense: HostSuspenseBoundary | null,
  1427. doRemove?: boolean
  1428. ) {
  1429. const { props, ref, children, dynamicChildren, shapeFlag } = vnode
  1430. // unset ref
  1431. if (ref !== null && parentComponent !== null) {
  1432. setRef(ref, null, parentComponent, null)
  1433. }
  1434. if (shapeFlag & ShapeFlags.COMPONENT) {
  1435. if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
  1436. ;(parentComponent!.sink as KeepAliveSink).deactivate(vnode)
  1437. } else {
  1438. unmountComponent(vnode.component!, parentSuspense, doRemove)
  1439. }
  1440. return
  1441. }
  1442. if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
  1443. vnode.suspense!.unmount(parentSuspense, doRemove)
  1444. return
  1445. }
  1446. if (props != null && props.onVnodeBeforeUnmount != null) {
  1447. invokeDirectiveHook(props.onVnodeBeforeUnmount, parentComponent, vnode)
  1448. }
  1449. if (dynamicChildren != null) {
  1450. // fast path for block nodes: only need to unmount dynamic children.
  1451. unmountChildren(dynamicChildren, parentComponent, parentSuspense)
  1452. } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  1453. unmountChildren(children as HostVNode[], parentComponent, parentSuspense)
  1454. }
  1455. if (doRemove) {
  1456. remove(vnode)
  1457. }
  1458. if (props != null && props.onVnodeUnmounted != null) {
  1459. queuePostRenderEffect(() => {
  1460. invokeDirectiveHook(props.onVnodeUnmounted!, parentComponent, vnode)
  1461. }, parentSuspense)
  1462. }
  1463. }
  1464. function remove(vnode: HostVNode) {
  1465. const { type, el, anchor, children, transition } = vnode
  1466. const performRemove = () => {
  1467. hostRemove(el!)
  1468. if (anchor != null) hostRemove(anchor)
  1469. if (
  1470. transition != null &&
  1471. !transition.persisted &&
  1472. transition.afterLeave
  1473. ) {
  1474. transition.afterLeave()
  1475. }
  1476. }
  1477. if (type === Fragment) {
  1478. performRemove()
  1479. removeChildren(children as HostVNode[])
  1480. return
  1481. }
  1482. if (
  1483. vnode.shapeFlag & ShapeFlags.ELEMENT &&
  1484. transition != null &&
  1485. !transition.persisted
  1486. ) {
  1487. const { leave, delayLeave } = transition
  1488. const performLeave = () => leave(el!, performRemove)
  1489. if (delayLeave) {
  1490. delayLeave(vnode.el!, performRemove, performLeave)
  1491. } else {
  1492. performLeave()
  1493. }
  1494. } else {
  1495. performRemove()
  1496. }
  1497. }
  1498. function removeChildren(children: HostVNode[]) {
  1499. for (let i = 0; i < children.length; i++) {
  1500. remove(children[i])
  1501. }
  1502. }
  1503. function unmountComponent(
  1504. instance: ComponentInternalInstance,
  1505. parentSuspense: HostSuspenseBoundary | null,
  1506. doRemove?: boolean
  1507. ) {
  1508. if (__HMR__ && instance.type.__hmrId != null) {
  1509. unregisterHMR(instance)
  1510. }
  1511. const { bum, effects, update, subTree, um, da, isDeactivated } = instance
  1512. // beforeUnmount hook
  1513. if (bum !== null) {
  1514. invokeHooks(bum)
  1515. }
  1516. if (effects !== null) {
  1517. for (let i = 0; i < effects.length; i++) {
  1518. stop(effects[i])
  1519. }
  1520. }
  1521. // update may be null if a component is unmounted before its async
  1522. // setup has resolved.
  1523. if (update !== null) {
  1524. stop(update)
  1525. unmount(subTree, instance, parentSuspense, doRemove)
  1526. }
  1527. // unmounted hook
  1528. if (um !== null) {
  1529. queuePostRenderEffect(um, parentSuspense)
  1530. }
  1531. // deactivated hook
  1532. if (
  1533. da !== null &&
  1534. !isDeactivated &&
  1535. instance.vnode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
  1536. ) {
  1537. queuePostRenderEffect(da, parentSuspense)
  1538. }
  1539. queuePostFlushCb(() => {
  1540. instance.isUnmounted = true
  1541. })
  1542. // A component with async dep inside a pending suspense is unmounted before
  1543. // its async dep resolves. This should remove the dep from the suspense, and
  1544. // cause the suspense to resolve immediately if that was the last dep.
  1545. if (
  1546. __FEATURE_SUSPENSE__ &&
  1547. parentSuspense !== null &&
  1548. !parentSuspense.isResolved &&
  1549. !parentSuspense.isUnmounted &&
  1550. instance.asyncDep !== null &&
  1551. !instance.asyncResolved
  1552. ) {
  1553. parentSuspense.deps--
  1554. if (parentSuspense.deps === 0) {
  1555. parentSuspense.resolve()
  1556. }
  1557. }
  1558. }
  1559. function unmountChildren(
  1560. children: HostVNode[],
  1561. parentComponent: ComponentInternalInstance | null,
  1562. parentSuspense: HostSuspenseBoundary | null,
  1563. doRemove?: boolean,
  1564. start: number = 0
  1565. ) {
  1566. for (let i = start; i < children.length; i++) {
  1567. unmount(children[i], parentComponent, parentSuspense, doRemove)
  1568. }
  1569. }
  1570. function getNextHostNode(vnode: HostVNode): HostNode | null {
  1571. if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
  1572. return getNextHostNode(vnode.component!.subTree)
  1573. }
  1574. if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
  1575. return vnode.suspense!.next()
  1576. }
  1577. return hostNextSibling((vnode.anchor || vnode.el)!)
  1578. }
  1579. function setRef(
  1580. ref: string | Function | Ref,
  1581. oldRef: string | Function | Ref | null,
  1582. parent: ComponentInternalInstance,
  1583. value: HostNode | ComponentPublicInstance | null
  1584. ) {
  1585. const refs = parent.refs === EMPTY_OBJ ? (parent.refs = {}) : parent.refs
  1586. const renderContext = toRaw(parent.renderContext)
  1587. // unset old ref
  1588. if (oldRef !== null && oldRef !== ref) {
  1589. if (isString(oldRef)) {
  1590. refs[oldRef] = null
  1591. const oldSetupRef = renderContext[oldRef]
  1592. if (isRef(oldSetupRef)) {
  1593. oldSetupRef.value = null
  1594. }
  1595. } else if (isRef(oldRef)) {
  1596. oldRef.value = null
  1597. }
  1598. }
  1599. if (isString(ref)) {
  1600. const setupRef = renderContext[ref]
  1601. if (isRef(setupRef)) {
  1602. setupRef.value = value
  1603. }
  1604. refs[ref] = value
  1605. } else if (isRef(ref)) {
  1606. ref.value = value
  1607. } else if (isFunction(ref)) {
  1608. callWithErrorHandling(ref, parent, ErrorCodes.FUNCTION_REF, [value, refs])
  1609. } else if (__DEV__) {
  1610. warn('Invalid template ref type:', value, `(${typeof value})`)
  1611. }
  1612. }
  1613. const render: RootRenderFunction<
  1614. HostNode,
  1615. HostElement & {
  1616. _vnode: HostVNode | null
  1617. }
  1618. > = (vnode, container) => {
  1619. if (vnode == null) {
  1620. if (container._vnode) {
  1621. unmount(container._vnode, null, null, true)
  1622. }
  1623. } else {
  1624. patch(container._vnode || null, vnode, container)
  1625. }
  1626. flushPostFlushCbs()
  1627. container._vnode = vnode
  1628. }
  1629. return {
  1630. render,
  1631. createApp: createAppAPI(render)
  1632. }
  1633. }
  1634. // https://en.wikipedia.org/wiki/Longest_increasing_subsequence
  1635. function getSequence(arr: number[]): number[] {
  1636. const p = arr.slice()
  1637. const result = [0]
  1638. let i, j, u, v, c
  1639. const len = arr.length
  1640. for (i = 0; i < len; i++) {
  1641. const arrI = arr[i]
  1642. if (arrI !== 0) {
  1643. j = result[result.length - 1]
  1644. if (arr[j] < arrI) {
  1645. p[i] = j
  1646. result.push(i)
  1647. continue
  1648. }
  1649. u = 0
  1650. v = result.length - 1
  1651. while (u < v) {
  1652. c = ((u + v) / 2) | 0
  1653. if (arr[result[c]] < arrI) {
  1654. u = c + 1
  1655. } else {
  1656. v = c
  1657. }
  1658. }
  1659. if (arrI < arr[result[u]]) {
  1660. if (u > 0) {
  1661. p[i] = result[u - 1]
  1662. }
  1663. result[u] = i
  1664. }
  1665. }
  1666. }
  1667. u = result.length
  1668. v = result[u - 1]
  1669. while (u-- > 0) {
  1670. result[u] = v
  1671. v = p[v]
  1672. }
  1673. return result
  1674. }