Skip to content

Commit 07059b1

Browse files
committed
feat: slot type for defineComponent
1 parent 4e65383 commit 07059b1

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
@@ -1769,6 +1769,51 @@ return { props, emit }
17691769
})"
17701770
`;
17711771

1772+
exports[`SFC compile <script setup> > with TypeScript > defineSlots 1`] = `
1773+
"import { useSlots as _useSlots, defineComponent as _defineComponent } from 'vue'
1774+
1775+
export default /*#__PURE__*/_defineComponent({
1776+
setup(__props, { expose: __expose }) {
1777+
__expose();
1778+
1779+
const slots = _useSlots()
1780+
1781+
return { slots }
1782+
}
1783+
1784+
})"
1785+
`;
1786+
1787+
exports[`SFC compile <script setup> > with TypeScript > defineSlots w/o generic params 1`] = `
1788+
"import { useSlots as _useSlots } from 'vue'
1789+
1790+
export default {
1791+
setup(__props, { expose: __expose }) {
1792+
__expose();
1793+
1794+
const slots = _useSlots()
1795+
1796+
return { slots }
1797+
}
1798+
1799+
}"
1800+
`;
1801+
1802+
exports[`SFC compile <script setup> > with TypeScript > defineSlots w/o return value 1`] = `
1803+
"import { defineComponent as _defineComponent } from 'vue'
1804+
1805+
export default /*#__PURE__*/_defineComponent({
1806+
setup(__props, { expose: __expose }) {
1807+
__expose();
1808+
1809+
1810+
1811+
return { }
1812+
}
1813+
1814+
})"
1815+
`;
1816+
17721817
exports[`SFC compile <script setup> > with TypeScript > hoist type declarations 1`] = `
17731818
"import { defineComponent as _defineComponent } from 'vue'
17741819
export interface Foo {}

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1540,6 +1540,40 @@ const emit = defineEmits(['a', 'b'])
15401540
expect(content).toMatch(`emits: ['foo']`)
15411541
})
15421542

1543+
test('defineSlots', () => {
1544+
const { content } = compile(`
1545+
<script setup lang="ts">
1546+
const slots = defineSlots<{
1547+
default: [msg: string]
1548+
}>()
1549+
</script>
1550+
`)
1551+
assertCode(content)
1552+
expect(content).toMatch(`const slots = _useSlots()`)
1553+
})
1554+
1555+
test('defineSlots w/o return value', () => {
1556+
const { content } = compile(`
1557+
<script setup lang="ts">
1558+
defineSlots<{
1559+
default: [msg: string]
1560+
}>()
1561+
</script>
1562+
`)
1563+
assertCode(content)
1564+
expect(content).not.toMatch(`_useSlots`)
1565+
})
1566+
1567+
test('defineSlots w/o generic params', () => {
1568+
const { content } = compile(`
1569+
<script setup>
1570+
const slots = defineSlots()
1571+
</script>
1572+
`)
1573+
assertCode(content)
1574+
expect(content).toMatch(`const slots = _useSlots()`)
1575+
})
1576+
15431577
test('runtime Enum', () => {
15441578
const { content, bindings } = compile(
15451579
`<script setup lang="ts">

packages/compiler-sfc/src/compileScript.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ export function compileScript(
604604
}
605605
hasDefineSlotsCall = true
606606

607-
if (node.arguments) {
607+
if (node.arguments.length > 0) {
608608
error(`${DEFINE_SLOTS}() cannot accept arguments`, node)
609609
}
610610

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

@@ -160,6 +162,19 @@ describe('defineEmits w/ runtime declaration', () => {
160162
emit2('baz')
161163
})
162164

165+
describe('defineSlots', () => {
166+
const slots = defineSlots<{
167+
default: [foo: string, bar: number]
168+
}>()
169+
expectType<(foo: string, bar: number) => VNode[]>(slots.default)
170+
171+
const slotsUntype = defineSlots()
172+
expectType<Slots>(slotsUntype)
173+
174+
// @ts-expect-error `default` should be an array
175+
defineSlots<{ default: string }>()
176+
})
177+
163178
describe('useAttrs', () => {
164179
const attrs = useAttrs()
165180
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
@@ -19,7 +19,7 @@ import {
1919
ExtractPropTypes
2020
} from './componentProps'
2121
import { warn } from './warning'
22-
import { VNode } from './vnode'
22+
import { Slot } from './componentSlots'
2323

2424
// dev only
2525
const warnRuntimeUsage = (method: string) =>
@@ -178,11 +178,11 @@ export function defineOptions<
178178
}
179179

180180
export function defineSlots<
181-
T extends Record<string, any>
181+
T extends Record<string, any[]> = Record<string, any[]>
182182
>(): // @ts-expect-error
183-
{
184-
[K in keyof T]: (scope: T[K]) => VNode[] | undefined
185-
} {
183+
Readonly<{
184+
[K in keyof T]: Slot<T[K]>
185+
}> {
186186
if (__DEV__) {
187187
warnRuntimeUsage(`defineSlots`)
188188
}

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)