wip(vapor): createIf

This commit is contained in:
Evan You 2024-12-14 20:37:43 +08:00
parent bcb9209c4c
commit 4318129b96
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
5 changed files with 50 additions and 25 deletions

View File

@ -169,10 +169,12 @@ describe('component: slots', () => {
}) })
test('slot should be rendered correctly with slot props', async () => { test('slot should be rendered correctly with slot props', async () => {
const src = ref('header')
const Comp = defineVaporComponent(() => { const Comp = defineVaporComponent(() => {
const n0 = template('<div></div>')() const n0 = template('<div></div>')()
insert( insert(
createSlot('header', { title: () => 'header' }), createSlot('header', { title: () => src.value }),
n0 as any as ParentNode, n0 as any as ParentNode,
) )
return n0 return n0
@ -191,6 +193,10 @@ describe('component: slots', () => {
}).render() }).render()
expect(host.innerHTML).toBe('<div><h1>header</h1><!--slot--></div>') 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 () => { test('dynamic slot props', async () => {
@ -263,17 +269,23 @@ describe('component: slots', () => {
$: [ $: [
() => ({ () => ({
name: 'header', name: 'header',
fn: (props: any) => template(props.title)(), fn: (props: any) => {
const el = template('<h1></h1>')()
renderEffect(() => {
setText(el, props.title)
})
return el
},
}), }),
], ],
}) })
}).render() }).render()
expect(host.innerHTML).toBe('<div>header<!--slot--></div>') expect(host.innerHTML).toBe('<div><h1>header</h1><!--slot--></div>')
val.value = 'footer' val.value = 'footer'
await nextTick() 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 () => { test('dynamic slot outlet should be render correctly with slot props', async () => {

View File

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

View File

@ -1,4 +1,5 @@
import type { BlockFn, Fragment } from './block' import { type BlockFn, DynamicFragment } from './block'
import { renderEffect } from './renderEffect'
export function createIf( export function createIf(
condition: () => any, condition: () => any,
@ -6,6 +7,12 @@ export function createIf(
b2?: BlockFn, b2?: BlockFn,
once?: boolean, once?: boolean,
// hydrationNode?: Node, // hydrationNode?: Node,
): Fragment { ): DynamicFragment {
return [] as any const frag = __DEV__ ? new DynamicFragment('if') : new DynamicFragment()
if (once) {
frag.update(condition() ? b1 : b2)
} else {
renderEffect(() => frag.update(condition() ? b1 : b2))
}
return frag
} }

View File

@ -6,7 +6,7 @@ import {
unmountComponent, unmountComponent,
} from './component' } from './component'
import { createComment } from './dom/node' import { createComment } from './dom/node'
import { EffectScope } from '@vue/reactivity' import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
export type Block = export type Block =
| Node | Node
@ -29,7 +29,7 @@ export class Fragment {
export class DynamicFragment extends Fragment { export class DynamicFragment extends Fragment {
anchor: Node anchor: Node
scope: EffectScope | undefined scope: EffectScope | undefined
key: any current?: BlockFn
constructor(anchorLabel?: string) { constructor(anchorLabel?: string) {
super([]) super([])
@ -40,10 +40,13 @@ export class DynamicFragment extends Fragment {
document.createTextNode('') document.createTextNode('')
} }
update(render?: BlockFn, key: any = render): void { update(render?: BlockFn): void {
if (key === this.key) return if (render === this.current) {
this.key = key return
}
this.current = render
pauseTracking()
const parent = this.anchor.parentNode const parent = this.anchor.parentNode
// teardown previous branch // teardown previous branch
@ -60,6 +63,7 @@ export class DynamicFragment extends Fragment {
this.scope = undefined this.scope = undefined
this.nodes = [] this.nodes = []
} }
resetTracking()
} }
} }

View File

@ -109,26 +109,28 @@ export function createSlot(
fallback?: Slot, fallback?: Slot,
): Block { ): Block {
const instance = currentInstance as VaporComponentInstance 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 const slotProps = rawProps
? new Proxy(rawProps, dynamicSlotsPropsProxyHandlers) ? new Proxy(rawProps, dynamicSlotsPropsProxyHandlers)
: EMPTY_OBJ : EMPTY_OBJ
// always create effect because a slot may contain dynamic root inside const renderSlot = (name: string) => {
// which affects fallback const slot = getSlot(rawSlots, name)
renderEffect(() => {
const slot = getSlot(instance.rawSlots, isFunction(name) ? name() : name)
if (slot) { if (slot) {
fragment.update( fragment.update(() => slot(slotProps) || (fallback && fallback()))
() => slot(slotProps) || (fallback && fallback()),
// TODO this key needs to account for possible fallback (v-if)
// inside the slot
slot,
)
} else { } else {
fragment.update(fallback) fragment.update(fallback)
} }
}) }
// dynamic slot name or has dynamicSlots
if (isDynamicName || rawSlots.$) {
renderEffect(() => renderSlot(isFunction(name) ? name() : name))
} else {
renderSlot(name)
}
return fragment return fragment
} }