Evan You 1 год назад
Родитель
Сommit
4318129b96

+ 16 - 4
packages/runtime-vapor/__tests__/componentSlots.spec.ts

@@ -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 () => {

+ 1 - 1
packages/runtime-vapor/__tests__/if.spec.ts

@@ -15,7 +15,7 @@ import { unmountComponent } from '../src/component'
 
 const define = makeRender()
 
-describe.todo('createIf', () => {
+describe('createIf', () => {
   test('basic', async () => {
     // mock this template:
     //  <div>

+ 10 - 3
packages/runtime-vapor/src/apiCreateIf.ts

@@ -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
 }

+ 9 - 5
packages/runtime-vapor/src/block.ts

@@ -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()
   }
 }
 

+ 14 - 12
packages/runtime-vapor/src/componentSlots.ts

@@ -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
 }