mirror of https://github.com/vuejs/core.git
feat(runtime-vapor): component slot (#143)
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
parent
bd888b9b1e
commit
78f74ce241
|
@ -12,6 +12,7 @@ import {
|
||||||
import { genExpression } from './expression'
|
import { genExpression } from './expression'
|
||||||
import { genPropKey } from './prop'
|
import { genPropKey } from './prop'
|
||||||
|
|
||||||
|
// TODO: generate component slots
|
||||||
export function genCreateComponent(
|
export function genCreateComponent(
|
||||||
oper: CreateComponentIRNode,
|
oper: CreateComponentIRNode,
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
|
|
|
@ -6,7 +6,6 @@ import {
|
||||||
createComponent,
|
createComponent,
|
||||||
createTextNode,
|
createTextNode,
|
||||||
createVaporApp,
|
createVaporApp,
|
||||||
getCurrentInstance,
|
|
||||||
hasInjectionContext,
|
hasInjectionContext,
|
||||||
inject,
|
inject,
|
||||||
nextTick,
|
nextTick,
|
||||||
|
|
|
@ -40,6 +40,8 @@ describe('attribute fallthrough', () => {
|
||||||
id: () => _ctx.id,
|
id: () => _ctx.id,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -85,6 +87,8 @@ describe('attribute fallthrough', () => {
|
||||||
id: () => _ctx.id,
|
id: () => _ctx.id,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -123,6 +127,8 @@ describe('attribute fallthrough', () => {
|
||||||
'custom-attr': () => 'custom-attr',
|
'custom-attr': () => 'custom-attr',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
return n0
|
return n0
|
||||||
|
@ -144,6 +150,8 @@ describe('attribute fallthrough', () => {
|
||||||
id: () => _ctx.id,
|
id: () => _ctx.id,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -244,6 +244,8 @@ describe('component: props', () => {
|
||||||
foo: () => _ctx.foo,
|
foo: () => _ctx.foo,
|
||||||
id: () => _ctx.id,
|
id: () => _ctx.id,
|
||||||
},
|
},
|
||||||
|
null,
|
||||||
|
null,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,191 @@
|
||||||
|
// NOTE: This test is implemented based on the case of `runtime-core/__test__/componentSlots.spec.ts`.
|
||||||
|
|
||||||
|
import {
|
||||||
|
createComponent,
|
||||||
|
createVaporApp,
|
||||||
|
defineComponent,
|
||||||
|
getCurrentInstance,
|
||||||
|
nextTick,
|
||||||
|
ref,
|
||||||
|
template,
|
||||||
|
} from '../src'
|
||||||
|
import { makeRender } from './_utils'
|
||||||
|
|
||||||
|
const define = makeRender<any>()
|
||||||
|
function renderWithSlots(slots: any): any {
|
||||||
|
let instance: any
|
||||||
|
const Comp = defineComponent({
|
||||||
|
render() {
|
||||||
|
const t0 = template('<div></div>')
|
||||||
|
const n0 = t0()
|
||||||
|
instance = getCurrentInstance()
|
||||||
|
return n0
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { render } = define({
|
||||||
|
render() {
|
||||||
|
return createComponent(Comp, {}, slots)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
render()
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('component: slots', () => {
|
||||||
|
test('initSlots: instance.slots should be set correctly', () => {
|
||||||
|
const { slots } = renderWithSlots({ _: 1 })
|
||||||
|
expect(slots).toMatchObject({ _: 1 })
|
||||||
|
})
|
||||||
|
|
||||||
|
// NOTE: slot normalization is not supported
|
||||||
|
test.todo(
|
||||||
|
'initSlots: should normalize object slots (when value is null, string, array)',
|
||||||
|
() => {},
|
||||||
|
)
|
||||||
|
test.todo(
|
||||||
|
'initSlots: should normalize object slots (when value is function)',
|
||||||
|
() => {},
|
||||||
|
)
|
||||||
|
|
||||||
|
test('initSlots: instance.slots should be set correctly', () => {
|
||||||
|
let instance: any
|
||||||
|
const Comp = defineComponent({
|
||||||
|
render() {
|
||||||
|
const t0 = template('<div></div>')
|
||||||
|
const n0 = t0()
|
||||||
|
instance = getCurrentInstance()
|
||||||
|
return n0
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { render } = define({
|
||||||
|
render() {
|
||||||
|
return createComponent(Comp, {}, { header: () => template('header')() })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
render()
|
||||||
|
|
||||||
|
expect(instance.slots.header()).toMatchObject(
|
||||||
|
document.createTextNode('header'),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// runtime-core's "initSlots: instance.slots should be set correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)"
|
||||||
|
test('initSlots: instance.slots should be set correctly', () => {
|
||||||
|
const { slots } = renderWithSlots({
|
||||||
|
default: () => template('<span></span>')(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// expect(
|
||||||
|
// '[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.',
|
||||||
|
// ).toHaveBeenWarned()
|
||||||
|
|
||||||
|
expect(slots.default()).toMatchObject(document.createElement('span'))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('updateSlots: instance.slots should be updated correctly', async () => {
|
||||||
|
const flag1 = ref(true)
|
||||||
|
|
||||||
|
let instance: any
|
||||||
|
const Child = () => {
|
||||||
|
instance = getCurrentInstance()
|
||||||
|
return template('child')()
|
||||||
|
}
|
||||||
|
|
||||||
|
const { render } = define({
|
||||||
|
render() {
|
||||||
|
return createComponent(Child, {}, { _: 2 as any }, () => [
|
||||||
|
flag1.value
|
||||||
|
? { name: 'one', fn: () => template('<span></span>')() }
|
||||||
|
: { name: 'two', fn: () => template('<div></div>')() },
|
||||||
|
])
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
render()
|
||||||
|
|
||||||
|
expect(instance.slots).toHaveProperty('one')
|
||||||
|
expect(instance.slots).not.toHaveProperty('two')
|
||||||
|
|
||||||
|
flag1.value = false
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(instance.slots).not.toHaveProperty('one')
|
||||||
|
expect(instance.slots).toHaveProperty('two')
|
||||||
|
})
|
||||||
|
|
||||||
|
// NOTE: it is not supported
|
||||||
|
// test('updateSlots: instance.slots should be updated correctly (when slotType is null)', () => {})
|
||||||
|
|
||||||
|
// runtime-core's "updateSlots: instance.slots should be update correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)"
|
||||||
|
test('updateSlots: instance.slots should be update correctly', async () => {
|
||||||
|
const flag1 = ref(true)
|
||||||
|
|
||||||
|
let instance: any
|
||||||
|
const Child = () => {
|
||||||
|
instance = getCurrentInstance()
|
||||||
|
return template('child')()
|
||||||
|
}
|
||||||
|
|
||||||
|
const { render } = define({
|
||||||
|
setup() {
|
||||||
|
return createComponent(Child, {}, {}, () => [
|
||||||
|
flag1.value
|
||||||
|
? [{ name: 'header', fn: () => template('header')() }]
|
||||||
|
: [{ name: 'footer', fn: () => template('footer')() }],
|
||||||
|
])
|
||||||
|
},
|
||||||
|
})
|
||||||
|
render()
|
||||||
|
|
||||||
|
expect(instance.slots).toHaveProperty('header')
|
||||||
|
flag1.value = false
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
// expect(
|
||||||
|
// '[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.',
|
||||||
|
// ).toHaveBeenWarned()
|
||||||
|
|
||||||
|
expect(instance.slots).toHaveProperty('footer')
|
||||||
|
})
|
||||||
|
|
||||||
|
test.todo('should respect $stable flag', async () => {
|
||||||
|
// TODO: $stable flag?
|
||||||
|
})
|
||||||
|
|
||||||
|
test.todo('should not warn when mounting another app in setup', () => {
|
||||||
|
// TODO: warning
|
||||||
|
const Comp = defineComponent({
|
||||||
|
render() {
|
||||||
|
const i = getCurrentInstance()
|
||||||
|
return i!.slots.default!()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const mountComp = () => {
|
||||||
|
createVaporApp({
|
||||||
|
render() {
|
||||||
|
return createComponent(
|
||||||
|
Comp,
|
||||||
|
{},
|
||||||
|
{ default: () => template('msg')() },
|
||||||
|
)!
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
mountComp()
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return null!
|
||||||
|
},
|
||||||
|
}
|
||||||
|
createVaporApp(App).mount(document.createElement('div'))
|
||||||
|
expect(
|
||||||
|
'Slot "default" invoked outside of the render function',
|
||||||
|
).not.toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
})
|
|
@ -5,17 +5,22 @@ import {
|
||||||
} from './component'
|
} from './component'
|
||||||
import { setupComponent } from './apiRender'
|
import { setupComponent } from './apiRender'
|
||||||
import type { RawProps } from './componentProps'
|
import type { RawProps } from './componentProps'
|
||||||
|
import type { DynamicSlots, Slots } from './componentSlots'
|
||||||
import { withAttrs } from './componentAttrs'
|
import { withAttrs } from './componentAttrs'
|
||||||
|
|
||||||
export function createComponent(
|
export function createComponent(
|
||||||
comp: Component,
|
comp: Component,
|
||||||
rawProps: RawProps | null = null,
|
rawProps: RawProps | null = null,
|
||||||
|
slots: Slots | null = null,
|
||||||
|
dynamicSlots: DynamicSlots | null = null,
|
||||||
singleRoot: boolean = false,
|
singleRoot: boolean = false,
|
||||||
) {
|
) {
|
||||||
const current = currentInstance!
|
const current = currentInstance!
|
||||||
const instance = createComponentInstance(
|
const instance = createComponentInstance(
|
||||||
comp,
|
comp,
|
||||||
singleRoot ? withAttrs(rawProps) : rawProps,
|
singleRoot ? withAttrs(rawProps) : rawProps,
|
||||||
|
slots,
|
||||||
|
dynamicSlots,
|
||||||
)
|
)
|
||||||
setupComponent(instance, singleRoot)
|
setupComponent(instance, singleRoot)
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,13 @@ export function createVaporApp(
|
||||||
|
|
||||||
mount(rootContainer): any {
|
mount(rootContainer): any {
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
instance = createComponentInstance(rootComponent, rootProps, context)
|
instance = createComponentInstance(
|
||||||
|
rootComponent,
|
||||||
|
rootProps,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
context,
|
||||||
|
)
|
||||||
setupComponent(instance)
|
setupComponent(instance)
|
||||||
render(instance, rootContainer)
|
render(instance, rootContainer)
|
||||||
return instance
|
return instance
|
||||||
|
|
|
@ -17,6 +17,12 @@ import {
|
||||||
emit,
|
emit,
|
||||||
normalizeEmitsOptions,
|
normalizeEmitsOptions,
|
||||||
} from './componentEmits'
|
} from './componentEmits'
|
||||||
|
import {
|
||||||
|
type DynamicSlots,
|
||||||
|
type InternalSlots,
|
||||||
|
type Slots,
|
||||||
|
initSlots,
|
||||||
|
} from './componentSlots'
|
||||||
import { VaporLifecycleHooks } from './apiLifecycle'
|
import { VaporLifecycleHooks } from './apiLifecycle'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { type AppContext, createAppContext } from './apiCreateVaporApp'
|
import { type AppContext, createAppContext } from './apiCreateVaporApp'
|
||||||
|
@ -32,7 +38,7 @@ export type SetupContext<E = EmitsOptions> = E extends any
|
||||||
attrs: Data
|
attrs: Data
|
||||||
emit: EmitFn<E>
|
emit: EmitFn<E>
|
||||||
expose: (exposed?: Record<string, any>) => void
|
expose: (exposed?: Record<string, any>) => void
|
||||||
// TODO slots
|
slots: Readonly<InternalSlots>
|
||||||
}
|
}
|
||||||
: never
|
: never
|
||||||
|
|
||||||
|
@ -46,6 +52,9 @@ export function createSetupContext(
|
||||||
get attrs() {
|
get attrs() {
|
||||||
return getAttrsProxy(instance)
|
return getAttrsProxy(instance)
|
||||||
},
|
},
|
||||||
|
get slots() {
|
||||||
|
return getSlotsProxy(instance)
|
||||||
|
},
|
||||||
get emit() {
|
get emit() {
|
||||||
return (event: string, ...args: any[]) => instance.emit(event, ...args)
|
return (event: string, ...args: any[]) => instance.emit(event, ...args)
|
||||||
},
|
},
|
||||||
|
@ -57,6 +66,7 @@ export function createSetupContext(
|
||||||
return getAttrsProxy(instance)
|
return getAttrsProxy(instance)
|
||||||
},
|
},
|
||||||
emit: instance.emit,
|
emit: instance.emit,
|
||||||
|
slots: instance.slots,
|
||||||
expose: NOOP,
|
expose: NOOP,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,9 +112,11 @@ export interface ComponentInternalInstance {
|
||||||
emit: EmitFn
|
emit: EmitFn
|
||||||
emitted: Record<string, boolean> | null
|
emitted: Record<string, boolean> | null
|
||||||
attrs: Data
|
attrs: Data
|
||||||
|
slots: InternalSlots
|
||||||
refs: Data
|
refs: Data
|
||||||
|
|
||||||
attrsProxy: Data | null
|
attrsProxy?: Data
|
||||||
|
slotsProxy?: Slots
|
||||||
|
|
||||||
// lifecycle
|
// lifecycle
|
||||||
isMounted: boolean
|
isMounted: boolean
|
||||||
|
@ -188,6 +200,8 @@ let uid = 0
|
||||||
export function createComponentInstance(
|
export function createComponentInstance(
|
||||||
component: ObjectComponent | FunctionalComponent,
|
component: ObjectComponent | FunctionalComponent,
|
||||||
rawProps: RawProps | null,
|
rawProps: RawProps | null,
|
||||||
|
slots: Slots | null = null,
|
||||||
|
dynamicSlots: DynamicSlots | null = null,
|
||||||
// application root node only
|
// application root node only
|
||||||
appContext: AppContext | null = null,
|
appContext: AppContext | null = null,
|
||||||
): ComponentInternalInstance {
|
): ComponentInternalInstance {
|
||||||
|
@ -224,10 +238,9 @@ export function createComponentInstance(
|
||||||
emit: null!,
|
emit: null!,
|
||||||
emitted: null,
|
emitted: null,
|
||||||
attrs: EMPTY_OBJ,
|
attrs: EMPTY_OBJ,
|
||||||
|
slots: EMPTY_OBJ,
|
||||||
refs: EMPTY_OBJ,
|
refs: EMPTY_OBJ,
|
||||||
|
|
||||||
attrsProxy: null,
|
|
||||||
|
|
||||||
// lifecycle
|
// lifecycle
|
||||||
isMounted: false,
|
isMounted: false,
|
||||||
isUnmounted: false,
|
isUnmounted: false,
|
||||||
|
@ -283,6 +296,7 @@ export function createComponentInstance(
|
||||||
// [VaporLifecycleHooks.SERVER_PREFETCH]: null,
|
// [VaporLifecycleHooks.SERVER_PREFETCH]: null,
|
||||||
}
|
}
|
||||||
initProps(instance, rawProps, !isFunction(component))
|
initProps(instance, rawProps, !isFunction(component))
|
||||||
|
initSlots(instance, slots, dynamicSlots)
|
||||||
instance.emit = emit.bind(null, instance)
|
instance.emit = emit.bind(null, instance)
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
@ -315,3 +329,17 @@ function getAttrsProxy(instance: ComponentInternalInstance): Data {
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dev-only
|
||||||
|
*/
|
||||||
|
function getSlotsProxy(instance: ComponentInternalInstance): Slots {
|
||||||
|
return (
|
||||||
|
instance.slotsProxy ||
|
||||||
|
(instance.slotsProxy = new Proxy(instance.slots, {
|
||||||
|
get(target, key: string) {
|
||||||
|
return target[key]
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
import { type IfAny, extend, isArray } from '@vue/shared'
|
||||||
|
import { baseWatch } from '@vue/reactivity'
|
||||||
|
import type { ComponentInternalInstance } from './component'
|
||||||
|
import type { Block } from './apiRender'
|
||||||
|
import { createVaporPreScheduler } from './scheduler'
|
||||||
|
|
||||||
|
// TODO: SSR
|
||||||
|
|
||||||
|
export type Slot<T extends any = any> = (
|
||||||
|
...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>
|
||||||
|
) => Block
|
||||||
|
|
||||||
|
export type InternalSlots = {
|
||||||
|
[name: string]: Slot | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Slots = Readonly<InternalSlots>
|
||||||
|
|
||||||
|
export interface DynamicSlot {
|
||||||
|
name: string
|
||||||
|
fn: Slot
|
||||||
|
key?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DynamicSlots = () => (DynamicSlot | DynamicSlot[])[]
|
||||||
|
|
||||||
|
export const initSlots = (
|
||||||
|
instance: ComponentInternalInstance,
|
||||||
|
rawSlots: InternalSlots | null = null,
|
||||||
|
dynamicSlots: DynamicSlots | null = null,
|
||||||
|
) => {
|
||||||
|
const slots: InternalSlots = extend({}, rawSlots)
|
||||||
|
|
||||||
|
if (dynamicSlots) {
|
||||||
|
const dynamicSlotKeys: Record<string, true> = {}
|
||||||
|
baseWatch(
|
||||||
|
() => {
|
||||||
|
const _dynamicSlots = dynamicSlots()
|
||||||
|
for (let i = 0; i < _dynamicSlots.length; i++) {
|
||||||
|
const slot = _dynamicSlots[i]
|
||||||
|
// array of dynamic slot generated by <template v-for="..." #[...]>
|
||||||
|
if (isArray(slot)) {
|
||||||
|
for (let j = 0; j < slot.length; j++) {
|
||||||
|
slots[slot[j].name] = slot[j].fn
|
||||||
|
dynamicSlotKeys[slot[j].name] = true
|
||||||
|
}
|
||||||
|
} else if (slot) {
|
||||||
|
// conditional single slot generated by <template v-if="..." #foo>
|
||||||
|
slots[slot.name] = slot.key
|
||||||
|
? (...args: any[]) => {
|
||||||
|
const res = slot.fn(...args)
|
||||||
|
// attach branch key so each conditional branch is considered a
|
||||||
|
// different fragment
|
||||||
|
if (res) (res as any).key = slot.key
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
: slot.fn
|
||||||
|
dynamicSlotKeys[slot.name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// delete stale slots
|
||||||
|
for (const key in dynamicSlotKeys) {
|
||||||
|
if (
|
||||||
|
!_dynamicSlots.some(slot =>
|
||||||
|
isArray(slot)
|
||||||
|
? slot.some(s => s.name === key)
|
||||||
|
: slot?.name === key,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
delete slots[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
{ scheduler: createVaporPreScheduler(instance) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.slots = slots
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
// @ts-check
|
||||||
|
import {
|
||||||
|
children,
|
||||||
|
createComponent,
|
||||||
|
defineComponent,
|
||||||
|
insert,
|
||||||
|
on,
|
||||||
|
ref,
|
||||||
|
renderEffect,
|
||||||
|
setText,
|
||||||
|
template,
|
||||||
|
} from '@vue/vapor'
|
||||||
|
|
||||||
|
// <template #mySlot="{ message, changeMessage }">
|
||||||
|
// <div clas="slotted">
|
||||||
|
// <h1>{{ message }}</h1>
|
||||||
|
// <button @click="changeMessage">btn parent</button>
|
||||||
|
// </div>
|
||||||
|
// </template>
|
||||||
|
const t1 = template(
|
||||||
|
'<div class="slotted"><h1><!></h1><button>parent btn</button></div>',
|
||||||
|
)
|
||||||
|
|
||||||
|
const Parent = defineComponent({
|
||||||
|
vapor: true,
|
||||||
|
setup() {
|
||||||
|
return (() => {
|
||||||
|
/** @type {any} */
|
||||||
|
const n0 = createComponent(
|
||||||
|
Child,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
mySlot: ({ message, changeMessage }) => {
|
||||||
|
const n1 = t1()
|
||||||
|
const n2 = /** @type {any} */ (children(n1, 0))
|
||||||
|
const n3 = /** @type {any} */ (children(n1, 1))
|
||||||
|
renderEffect(() => setText(n2, message()))
|
||||||
|
on(n3, 'click', changeMessage)
|
||||||
|
return n1
|
||||||
|
},
|
||||||
|
// e.g. default slot
|
||||||
|
// default: () => {
|
||||||
|
// const n1 = t1()
|
||||||
|
// return n1
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return n0
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const t2 = template(
|
||||||
|
'<div class="child-container"><button>child btn</button></div>',
|
||||||
|
)
|
||||||
|
|
||||||
|
const Child = defineComponent({
|
||||||
|
vapor: true,
|
||||||
|
setup(_, { slots }) {
|
||||||
|
const message = ref('Hello World!')
|
||||||
|
function changeMessage() {
|
||||||
|
message.value += '!'
|
||||||
|
}
|
||||||
|
|
||||||
|
return (() => {
|
||||||
|
// <div>
|
||||||
|
// <slot name="mySlot" :message="msg" :changeMessage="changeMessage" />
|
||||||
|
// <button @click="changeMessage">button in child</button>
|
||||||
|
// </div>
|
||||||
|
const n0 = /** @type {any} */ (t2())
|
||||||
|
const n1 = /** @type {any} */ (children(n0, 0))
|
||||||
|
on(n1, 'click', () => changeMessage)
|
||||||
|
const s0 = /** @type {any} */ (
|
||||||
|
slots.mySlot?.({
|
||||||
|
message: () => message.value,
|
||||||
|
changeMessage: () => changeMessage,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
insert(s0, n0, n1)
|
||||||
|
return n0
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Parent
|
Loading…
Reference in New Issue