Skip to content

Commit 05c4dca

Browse files
committed
feat: slot type for defineComponent
1 parent 477dd22 commit 05c4dca

File tree

13 files changed

+229
-30
lines changed

13 files changed

+229
-30
lines changed

packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1785,6 +1785,51 @@ return { props, emit }
17851785
})"
17861786
`;
17871787

1788+
exports[`SFC compile <script setup> > with TypeScript > defineSlots 1`] = `
1789+
"import { useSlots as _useSlots, defineComponent as _defineComponent } from 'vue'
1790+
1791+
export default /*#__PURE__*/_defineComponent({
1792+
setup(__props, { expose: __expose }) {
1793+
__expose();
1794+
1795+
const slots = _useSlots()
1796+
1797+
return { slots }
1798+
}
1799+
1800+
})"
1801+
`;
1802+
1803+
exports[`SFC compile <script setup> > with TypeScript > defineSlots w/o generic params 1`] = `
1804+
"import { useSlots as _useSlots } from 'vue'
1805+
1806+
export default {
1807+
setup(__props, { expose: __expose }) {
1808+
__expose();
1809+
1810+
const slots = _useSlots()
1811+
1812+
return { slots }
1813+
}
1814+
1815+
}"
1816+
`;
1817+
1818+
exports[`SFC compile <script setup> > with TypeScript > defineSlots w/o return value 1`] = `
1819+
"import { defineComponent as _defineComponent } from 'vue'
1820+
1821+
export default /*#__PURE__*/_defineComponent({
1822+
setup(__props, { expose: __expose }) {
1823+
__expose();
1824+
1825+
1826+
1827+
return { }
1828+
}
1829+
1830+
})"
1831+
`;
1832+
17881833
exports[`SFC compile <script setup> > with TypeScript > hoist type declarations 1`] = `
17891834
"import { defineComponent as _defineComponent } from 'vue'
17901835
export interface Foo {}

packages/compiler-sfc/__tests__/compileScript.spec.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,6 +1585,40 @@ const emit = defineEmits(['a', 'b'])
15851585
assertCode(content)
15861586
})
15871587

1588+
test('defineSlots', () => {
1589+
const { content } = compile(`
1590+
<script setup lang="ts">
1591+
const slots = defineSlots<{
1592+
default: [msg: string]
1593+
}>()
1594+
</script>
1595+
`)
1596+
assertCode(content)
1597+
expect(content).toMatch(`const slots = _useSlots()`)
1598+
})
1599+
1600+
test('defineSlots w/o return value', () => {
1601+
const { content } = compile(`
1602+
<script setup lang="ts">
1603+
defineSlots<{
1604+
default: [msg: string]
1605+
}>()
1606+
</script>
1607+
`)
1608+
assertCode(content)
1609+
expect(content).not.toMatch(`_useSlots`)
1610+
})
1611+
1612+
test('defineSlots w/o generic params', () => {
1613+
const { content } = compile(`
1614+
<script setup>
1615+
const slots = defineSlots()
1616+
</script>
1617+
`)
1618+
assertCode(content)
1619+
expect(content).toMatch(`const slots = _useSlots()`)
1620+
})
1621+
15881622
test('runtime Enum', () => {
15891623
const { content, bindings } = compile(
15901624
`<script setup lang="ts">

packages/compiler-sfc/src/compileScript.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,7 @@ export function compileScript(
601601
}
602602
hasDefineSlotsCall = true
603603

604-
if (node.arguments) {
604+
if (node.arguments.length > 0) {
605605
error(`${DEFINE_SLOTS}() cannot accept arguments`, node)
606606
}
607607

packages/dts-test/defineComponent.test-d.tsx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import {
88
ComponentPublicInstance,
99
ComponentOptions,
1010
SetupContext,
11-
h
11+
h,
12+
SlotsType,
13+
useSlots,
14+
Slots
1215
} from 'vue'
1316
import { describe, expectType, IsUnion } from './utils'
1417

@@ -1406,6 +1409,32 @@ export default {
14061409
})
14071410
}
14081411

1412+
describe('slots', () => {
1413+
defineComponent({
1414+
slots: Object as SlotsType<{
1415+
default: [foo: string, bar: number]
1416+
item: [number]
1417+
}>,
1418+
setup(props, { slots }) {
1419+
expectType<(foo: string, bar: number) => any>(slots.default)
1420+
expectType<(scope: number) => any>(slots.item)
1421+
}
1422+
})
1423+
1424+
defineComponent({
1425+
// @ts-expect-error `default` should be an array
1426+
slots: Object as SlotsType<{ default: string }>
1427+
})
1428+
1429+
defineComponent({
1430+
setup(props, { slots }) {
1431+
// unknown slots
1432+
expectType<Slots>(slots)
1433+
expectType<((...args: any[]) => any) | undefined>(slots.default)
1434+
}
1435+
})
1436+
})
1437+
14091438
import {
14101439
DefineComponent,
14111440
ComponentOptionsMixin,
@@ -1428,6 +1457,7 @@ declare const MyButton: DefineComponent<
14281457
ComponentOptionsMixin,
14291458
EmitsOptions,
14301459
string,
1460+
{},
14311461
VNodeProps & AllowedComponentProps & ComponentCustomProps,
14321462
Readonly<ExtractPropTypes<{}>>,
14331463
{}

packages/dts-test/setupHelpers.test-d.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import {
44
useAttrs,
55
useSlots,
66
withDefaults,
7-
Slots
7+
Slots,
8+
defineSlots,
9+
VNode
810
} from 'vue'
911
import { describe, expectType } from './utils'
1012

@@ -179,6 +181,19 @@ describe('defineEmits w/ runtime declaration', () => {
179181
emit2('baz')
180182
})
181183

184+
describe('defineSlots', () => {
185+
const slots = defineSlots<{
186+
default: [foo: string, bar: number]
187+
}>()
188+
expectType<(foo: string, bar: number) => VNode[]>(slots.default)
189+
190+
const slotsUntype = defineSlots()
191+
expectType<Slots>(slotsUntype)
192+
193+
// @ts-expect-error `default` should be an array
194+
defineSlots<{ default: string }>()
195+
})
196+
182197
describe('useAttrs', () => {
183198
const attrs = useAttrs()
184199
expectType<Record<string, unknown>>(attrs)

packages/runtime-core/src/apiDefineComponent.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
CreateComponentPublicInstance,
2929
ComponentPublicInstanceConstructor
3030
} from './componentPublicInstance'
31+
import { SlotsType } from './componentSlots'
3132

3233
export type PublicProps = VNodeProps &
3334
AllowedComponentProps &
@@ -43,6 +44,7 @@ export type DefineComponent<
4344
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
4445
E extends EmitsOptions = {},
4546
EE extends string = string,
47+
S extends SlotsType = {},
4648
PP = PublicProps,
4749
Props = Readonly<
4850
PropsOrPropOptions extends ComponentPropsOptions
@@ -61,6 +63,7 @@ export type DefineComponent<
6163
Mixin,
6264
Extends,
6365
E,
66+
S,
6467
PP & Props,
6568
Defaults,
6669
true
@@ -77,6 +80,7 @@ export type DefineComponent<
7780
Extends,
7881
E,
7982
EE,
83+
S,
8084
Defaults
8185
> &
8286
PP
@@ -91,29 +95,33 @@ export type DefineComponent<
9195
export function defineComponent<
9296
Props extends Record<string, any>,
9397
E extends EmitsOptions = {},
94-
EE extends string = string
98+
EE extends string = string,
99+
S extends SlotsType = {}
95100
>(
96101
setup: (
97102
props: Props,
98-
ctx: SetupContext<E>
103+
ctx: SetupContext<E, S>
99104
) => RenderFunction | Promise<RenderFunction>,
100105
options?: Pick<ComponentOptions, 'name' | 'inheritAttrs'> & {
101106
props?: (keyof Props)[]
102107
emits?: E | EE[]
108+
slots?: S
103109
}
104110
): (props: Props & EmitsToProps<E>) => any
105111
export function defineComponent<
106112
Props extends Record<string, any>,
107113
E extends EmitsOptions = {},
108-
EE extends string = string
114+
EE extends string = string,
115+
S extends SlotsType = {}
109116
>(
110117
setup: (
111118
props: Props,
112-
ctx: SetupContext<E>
119+
ctx: SetupContext<E, S>
113120
) => RenderFunction | Promise<RenderFunction>,
114121
options?: Pick<ComponentOptions, 'name' | 'inheritAttrs'> & {
115122
props?: ComponentObjectPropsOptions<Props>
116123
emits?: E | EE[]
124+
slots?: S
117125
}
118126
): (props: Props & EmitsToProps<E>) => any
119127

@@ -130,6 +138,7 @@ export function defineComponent<
130138
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
131139
E extends EmitsOptions = {},
132140
EE extends string = string,
141+
S extends SlotsType = {},
133142
I extends ComponentInjectOptions = {},
134143
II extends string = string
135144
>(
@@ -143,10 +152,11 @@ export function defineComponent<
143152
Extends,
144153
E,
145154
EE,
155+
S,
146156
I,
147157
II
148158
>
149-
): DefineComponent<Props, RawBindings, D, C, M, Mixin, Extends, E, EE>
159+
): DefineComponent<Props, RawBindings, D, C, M, Mixin, Extends, E, EE, S>
150160

151161
// overload 3: object format with array props declaration
152162
// props inferred as { [key in PropNames]?: any }
@@ -161,6 +171,7 @@ export function defineComponent<
161171
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
162172
E extends EmitsOptions = {},
163173
EE extends string = string,
174+
S extends SlotsType = {},
164175
I extends ComponentInjectOptions = {},
165176
II extends string = string
166177
>(
@@ -174,6 +185,7 @@ export function defineComponent<
174185
Extends,
175186
E,
176187
EE,
188+
S,
177189
I,
178190
II
179191
>
@@ -186,7 +198,8 @@ export function defineComponent<
186198
Mixin,
187199
Extends,
188200
E,
189-
EE
201+
EE,
202+
S
190203
>
191204

192205
// overload 4: object format with object props declaration
@@ -203,6 +216,7 @@ export function defineComponent<
203216
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
204217
E extends EmitsOptions = {},
205218
EE extends string = string,
219+
S extends SlotsType = {},
206220
I extends ComponentInjectOptions = {},
207221
II extends string = string
208222
>(
@@ -216,10 +230,11 @@ export function defineComponent<
216230
Extends,
217231
E,
218232
EE,
233+
S,
219234
I,
220235
II
221236
>
222-
): DefineComponent<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE>
237+
): DefineComponent<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE, S>
223238

224239
// implementation, close to no-op
225240
export function defineComponent(

packages/runtime-core/src/apiSetupHelpers.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
ExtractPropTypes
2626
} from './componentProps'
2727
import { warn } from './warning'
28-
import { VNode } from './vnode'
28+
import { Slot } from './componentSlots'
2929

3030
// dev only
3131
const warnRuntimeUsage = (method: string) =>
@@ -194,11 +194,11 @@ export function defineOptions<
194194
}
195195

196196
export function defineSlots<
197-
T extends Record<string, any>
197+
T extends Record<string, any[]> = Record<string, any[]>
198198
>(): // @ts-expect-error
199-
{
200-
[K in keyof T]: (scope: T[K]) => VNode[] | undefined
201-
} {
199+
Readonly<{
200+
[K in keyof T]: Slot<T[K]>
201+
}> {
202202
if (__DEV__) {
203203
warnRuntimeUsage(`defineSlots`)
204204
}

packages/runtime-core/src/component.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@ import {
2727
initProps,
2828
normalizePropsOptions
2929
} from './componentProps'
30-
import { Slots, initSlots, InternalSlots } from './componentSlots'
30+
import {
31+
Slots,
32+
initSlots,
33+
InternalSlots,
34+
SlotsType,
35+
TypedSlots
36+
} from './componentSlots'
3137
import { warn } from './warning'
3238
import { ErrorCodes, callWithErrorHandling, handleError } from './errorHandling'
3339
import { AppContext, createAppContext, AppConfig } from './apiCreateApp'
@@ -117,10 +123,13 @@ export interface ComponentInternalOptions {
117123
__name?: string
118124
}
119125

120-
export interface FunctionalComponent<P = {}, E extends EmitsOptions = {}>
121-
extends ComponentInternalOptions {
126+
export interface FunctionalComponent<
127+
P = {},
128+
E extends EmitsOptions = {},
129+
S extends SlotsType = {}
130+
> extends ComponentInternalOptions {
122131
// use of any here is intentional so it can be a valid JSX Element constructor
123-
(props: P, ctx: Omit<SetupContext<E>, 'expose'>): any
132+
(props: P, ctx: Omit<SetupContext<E, S>, 'expose'>): any
124133
props?: ComponentPropsOptions<P>
125134
emits?: E | (keyof E)[]
126135
inheritAttrs?: boolean
@@ -168,10 +177,13 @@ export type { ComponentOptions }
168177
type LifecycleHook<TFn = Function> = TFn[] | null
169178

170179
// use `E extends any` to force evaluating type to fix #2362
171-
export type SetupContext<E = EmitsOptions> = E extends any
180+
export type SetupContext<
181+
E = EmitsOptions,
182+
S extends SlotsType = {}
183+
> = E extends any
172184
? {
173185
attrs: Data
174-
slots: Slots
186+
slots: [keyof S] extends [never] ? Slots : TypedSlots<S>
175187
emit: EmitFn<E>
176188
expose: (exposed?: Record<string, any>) => void
177189
}

0 commit comments

Comments
 (0)