]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(vapor): createIf
authorEvan You <evan@vuejs.org>
Sat, 14 Dec 2024 12:37:43 +0000 (20:37 +0800)
committerEvan You <evan@vuejs.org>
Sat, 14 Dec 2024 12:37:43 +0000 (20:37 +0800)
packages/runtime-vapor/__tests__/componentSlots.spec.ts
packages/runtime-vapor/__tests__/if.spec.ts
packages/runtime-vapor/src/apiCreateIf.ts
packages/runtime-vapor/src/block.ts
packages/runtime-vapor/src/componentSlots.ts

index 78be11ebe7f969a4afb9f010b5da457e90af44cf..868e45671c08f22ce71068e01da2a59118134955 100644 (file)
@@ -169,10 +169,12 @@ describe('component: slots', () => {
     })
 
     test('slot should be rendered correctly with slot props', async () => {
+      const src = ref('header')
+
       const Comp = defineVaporComponent(() => {
         const n0 = template('<div></div>')()
         insert(
-          createSlot('header', { title: () => 'header' }),
+          createSlot('header', { title: () => src.value }),
           n0 as any as ParentNode,
         )
         return n0
@@ -191,6 +193,10 @@ describe('component: slots', () => {
       }).render()
 
       expect(host.innerHTML).toBe('<div><h1>header</h1><!--slot--></div>')
+
+      src.value = 'footer'
+      await nextTick()
+      expect(host.innerHTML).toBe('<div><h1>footer</h1><!--slot--></div>')
     })
 
     test('dynamic slot props', async () => {
@@ -263,17 +269,23 @@ describe('component: slots', () => {
           $: [
             () => ({
               name: 'header',
-              fn: (props: any) => template(props.title)(),
+              fn: (props: any) => {
+                const el = template('<h1></h1>')()
+                renderEffect(() => {
+                  setText(el, props.title)
+                })
+                return el
+              },
             }),
           ],
         })
       }).render()
 
-      expect(host.innerHTML).toBe('<div>header<!--slot--></div>')
+      expect(host.innerHTML).toBe('<div><h1>header</h1><!--slot--></div>')
 
       val.value = 'footer'
       await nextTick()
-      expect(host.innerHTML).toBe('<div>footer<!--slot--></div>')
+      expect(host.innerHTML).toBe('<div><h1>footer</h1><!--slot--></div>')
     })
 
     test('dynamic slot outlet should be render correctly with slot props', async () => {
index 7acff485ac9dee8d7fe7cfa8befa53b377a6def8..a7e6266a7e145599098e7645061dc753f76457af 100644 (file)
@@ -15,7 +15,7 @@ import { unmountComponent } from '../src/component'
 
 const define = makeRender()
 
-describe.todo('createIf', () => {
+describe('createIf', () => {
   test('basic', async () => {
     // mock this template:
     //  <div>
index 7e566112470cf5e15778cd571a29fa85aeb92ea0..e4035313931877375d28345f851ad91d47974084 100644 (file)
@@ -1,4 +1,5 @@
-import type { BlockFn, Fragment } from './block'
+import { type BlockFn, DynamicFragment } from './block'
+import { renderEffect } from './renderEffect'
 
 export function createIf(
   condition: () => any,
@@ -6,6 +7,12 @@ export function createIf(
   b2?: BlockFn,
   once?: boolean,
   // hydrationNode?: Node,
-): Fragment {
-  return [] as any
+): DynamicFragment {
+  const frag = __DEV__ ? new DynamicFragment('if') : new DynamicFragment()
+  if (once) {
+    frag.update(condition() ? b1 : b2)
+  } else {
+    renderEffect(() => frag.update(condition() ? b1 : b2))
+  }
+  return frag
 }
index 65b7c70de7d323e2f2b52c4a33cacc53087b570e..eccd573322bad271da4e785b9003ffa8c7290c2a 100644 (file)
@@ -6,7 +6,7 @@ import {
   unmountComponent,
 } from './component'
 import { createComment } from './dom/node'
-import { EffectScope } from '@vue/reactivity'
+import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
 
 export type Block =
   | Node
@@ -29,7 +29,7 @@ export class Fragment {
 export class DynamicFragment extends Fragment {
   anchor: Node
   scope: EffectScope | undefined
-  key: any
+  current?: BlockFn
 
   constructor(anchorLabel?: string) {
     super([])
@@ -40,10 +40,13 @@ export class DynamicFragment extends Fragment {
           document.createTextNode('')
   }
 
-  update(render?: BlockFn, key: any = render): void {
-    if (key === this.key) return
-    this.key = key
+  update(render?: BlockFn): void {
+    if (render === this.current) {
+      return
+    }
+    this.current = render
 
+    pauseTracking()
     const parent = this.anchor.parentNode
 
     // teardown previous branch
@@ -60,6 +63,7 @@ export class DynamicFragment extends Fragment {
       this.scope = undefined
       this.nodes = []
     }
+    resetTracking()
   }
 }
 
index 6eb365e004972949c2a7a6e9bce52a6c78ad964d..33d5ff23345506cbd98513437508a95db5980095 100644 (file)
@@ -109,26 +109,28 @@ export function createSlot(
   fallback?: Slot,
 ): Block {
   const instance = currentInstance as VaporComponentInstance
-  const fragment = new DynamicFragment('slot')
+  const rawSlots = instance.rawSlots
+  const isDynamicName = isFunction(name)
+  const fragment = __DEV__ ? new DynamicFragment('slot') : new DynamicFragment()
   const slotProps = rawProps
     ? new Proxy(rawProps, dynamicSlotsPropsProxyHandlers)
     : EMPTY_OBJ
 
-  // always create effect because a slot may contain dynamic root inside
-  // which affects fallback
-  renderEffect(() => {
-    const slot = getSlot(instance.rawSlots, isFunction(name) ? name() : name)
+  const renderSlot = (name: string) => {
+    const slot = getSlot(rawSlots, name)
     if (slot) {
-      fragment.update(
-        () => slot(slotProps) || (fallback && fallback()),
-        // TODO this key needs to account for possible fallback (v-if)
-        // inside the slot
-        slot,
-      )
+      fragment.update(() => slot(slotProps) || (fallback && fallback()))
     } else {
       fragment.update(fallback)
     }
-  })
+  }
+
+  // dynamic slot name or has dynamicSlots
+  if (isDynamicName || rawSlots.$) {
+    renderEffect(() => renderSlot(isFunction(name) ? name() : name))
+  } else {
+    renderSlot(name)
+  }
 
   return fragment
 }