Skip to content

Commit

Permalink
feat: slot type for defineComponent
Browse files Browse the repository at this point in the history
  • Loading branch information
sxzz committed Mar 30, 2023
1 parent 98b5a3d commit af4880d
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1769,6 +1769,51 @@ return { props, emit }
})"
`;

exports[`SFC compile <script setup> > with TypeScript > defineSlots 1`] = `
"import { useSlots as _useSlots, defineComponent as _defineComponent } from 'vue'

export default /*#__PURE__*/_defineComponent({
setup(__props, { expose: __expose }) {
__expose();

const slots = _useSlots()

return { slots }
}

})"
`;

exports[`SFC compile <script setup> > with TypeScript > defineSlots w/o generic params 1`] = `
"import { useSlots as _useSlots } from 'vue'

export default {
setup(__props, { expose: __expose }) {
__expose();

const slots = _useSlots()

return { slots }
}

}"
`;

exports[`SFC compile <script setup> > with TypeScript > defineSlots w/o return value 1`] = `
"import { defineComponent as _defineComponent } from 'vue'

export default /*#__PURE__*/_defineComponent({
setup(__props, { expose: __expose }) {
__expose();



return { }
}

})"
`;

exports[`SFC compile <script setup> > with TypeScript > hoist type declarations 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
export interface Foo {}
Expand Down
34 changes: 34 additions & 0 deletions packages/compiler-sfc/__tests__/compileScript.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1540,6 +1540,40 @@ const emit = defineEmits(['a', 'b'])
expect(content).toMatch(`emits: ['foo']`)
})

test('defineSlots', () => {
const { content } = compile(`
<script setup lang="ts">
const slots = defineSlots<{
default: [msg: string]
}>()
</script>
`)
assertCode(content)
expect(content).toMatch(`const slots = _useSlots()`)
})

test('defineSlots w/o return value', () => {
const { content } = compile(`
<script setup lang="ts">
defineSlots<{
default: [msg: string]
}>()
</script>
`)
assertCode(content)
expect(content).not.toMatch(`_useSlots`)
})

test('defineSlots w/o generic params', () => {
const { content } = compile(`
<script setup>
const slots = defineSlots()
</script>
`)
assertCode(content)
expect(content).toMatch(`const slots = _useSlots()`)
})

test('runtime Enum', () => {
const { content, bindings } = compile(
`<script setup lang="ts">
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-sfc/src/compileScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,7 @@ export function compileScript(
}
hasDefineSlotsCall = true

if (node.arguments) {
if (node.arguments.length > 0) {
error(`${DEFINE_SLOTS}() cannot accept arguments`, node)
}

Expand Down
32 changes: 31 additions & 1 deletion packages/dts-test/defineComponent.test-d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import {
ComponentPublicInstance,
ComponentOptions,
SetupContext,
h
h,
SlotsType,
useSlots,
Slots
} from 'vue'
import { describe, expectType, IsUnion } from './utils'

Expand Down Expand Up @@ -1406,6 +1409,32 @@ export default {
})
}

describe('slots', () => {
defineComponent({
slots: Object as SlotsType<{
default: [foo: string, bar: number]
item: [number]
}>,
setup(props, { slots }) {
expectType<(foo: string, bar: number) => any>(slots.default)
expectType<(scope: number) => any>(slots.item)
}
})

defineComponent({
// @ts-expect-error `default` should be an array
slots: Object as SlotsType<{ default: string }>
})

defineComponent({
setup(props, { slots }) {
// unknown slots
expectType<Slots>(slots)
expectType<((...args: any[]) => any) | undefined>(slots.default)
}
})
})

import {
DefineComponent,
ComponentOptionsMixin,
Expand All @@ -1428,6 +1457,7 @@ declare const MyButton: DefineComponent<
ComponentOptionsMixin,
EmitsOptions,
string,
{},
VNodeProps & AllowedComponentProps & ComponentCustomProps,
Readonly<ExtractPropTypes<{}>>,
{}
Expand Down
17 changes: 16 additions & 1 deletion packages/dts-test/setupHelpers.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import {
useAttrs,
useSlots,
withDefaults,
Slots
Slots,
defineSlots,
VNode
} from 'vue'
import { describe, expectType } from './utils'

Expand Down Expand Up @@ -160,6 +162,19 @@ describe('defineEmits w/ runtime declaration', () => {
emit2('baz')
})

describe('defineSlots', () => {
const slots = defineSlots<{
default: [foo: string, bar: number]
}>()
expectType<(foo: string, bar: number) => VNode[]>(slots.default)

const slotsUntype = defineSlots()
expectType<Slots>(slotsUntype)

// @ts-expect-error `default` should be an array
defineSlots<{ default: string }>()
})

describe('useAttrs', () => {
const attrs = useAttrs()
expectType<Record<string, unknown>>(attrs)
Expand Down
29 changes: 22 additions & 7 deletions packages/runtime-core/src/apiDefineComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
CreateComponentPublicInstance,
ComponentPublicInstanceConstructor
} from './componentPublicInstance'
import { SlotsType } from './componentSlots'

export type PublicProps = VNodeProps &
AllowedComponentProps &
Expand All @@ -43,6 +44,7 @@ export type DefineComponent<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
S extends SlotsType = {},
PP = PublicProps,
Props = Readonly<
PropsOrPropOptions extends ComponentPropsOptions
Expand All @@ -61,6 +63,7 @@ export type DefineComponent<
Mixin,
Extends,
E,
S,
PP & Props,
Defaults,
true
Expand All @@ -77,6 +80,7 @@ export type DefineComponent<
Extends,
E,
EE,
S,
Defaults
> &
PP
Expand All @@ -91,29 +95,33 @@ export type DefineComponent<
export function defineComponent<
Props extends Record<string, any>,
E extends EmitsOptions = {},
EE extends string = string
EE extends string = string,
S extends SlotsType = {}
>(
setup: (
props: Props,
ctx: SetupContext<E>
ctx: SetupContext<E, S>
) => RenderFunction | Promise<RenderFunction>,
options?: Pick<ComponentOptions, 'name' | 'inheritAttrs'> & {
props?: (keyof Props)[]
emits?: E | EE[]
slots?: S
}
): (props: Props & EmitsToProps<E>) => any
export function defineComponent<
Props extends Record<string, any>,
E extends EmitsOptions = {},
EE extends string = string
EE extends string = string,
S extends SlotsType = {}
>(
setup: (
props: Props,
ctx: SetupContext<E>
ctx: SetupContext<E, S>
) => RenderFunction | Promise<RenderFunction>,
options?: Pick<ComponentOptions, 'name' | 'inheritAttrs'> & {
props?: ComponentObjectPropsOptions<Props>
emits?: E | EE[]
slots?: S
}
): (props: Props & EmitsToProps<E>) => any

Expand All @@ -130,6 +138,7 @@ export function defineComponent<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {},
II extends string = string
>(
Expand All @@ -143,10 +152,11 @@ export function defineComponent<
Extends,
E,
EE,
S,
I,
II
>
): DefineComponent<Props, RawBindings, D, C, M, Mixin, Extends, E, EE>
): DefineComponent<Props, RawBindings, D, C, M, Mixin, Extends, E, EE, S>

// overload 3: object format with array props declaration
// props inferred as { [key in PropNames]?: any }
Expand All @@ -161,6 +171,7 @@ export function defineComponent<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {},
II extends string = string
>(
Expand All @@ -174,6 +185,7 @@ export function defineComponent<
Extends,
E,
EE,
S,
I,
II
>
Expand All @@ -186,7 +198,8 @@ export function defineComponent<
Mixin,
Extends,
E,
EE
EE,
S
>

// overload 4: object format with object props declaration
Expand All @@ -203,6 +216,7 @@ export function defineComponent<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {},
II extends string = string
>(
Expand All @@ -216,10 +230,11 @@ export function defineComponent<
Extends,
E,
EE,
S,
I,
II
>
): DefineComponent<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE>
): DefineComponent<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE, S>

// implementation, close to no-op
export function defineComponent(
Expand Down
10 changes: 5 additions & 5 deletions packages/runtime-core/src/apiSetupHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
ExtractPropTypes
} from './componentProps'
import { warn } from './warning'
import { VNode } from './vnode'
import { Slot } from './componentSlots'

// dev only
const warnRuntimeUsage = (method: string) =>
Expand Down Expand Up @@ -178,11 +178,11 @@ export function defineOptions<
}

export function defineSlots<
T extends Record<string, any>
T extends Record<string, any[]> = Record<string, any[]>
>(): // @ts-expect-error
{
[K in keyof T]: (scope: T[K]) => VNode[] | undefined
} {
Readonly<{
[K in keyof T]: Slot<T[K]>
}> {
if (__DEV__) {
warnRuntimeUsage(`defineSlots`)
}
Expand Down
24 changes: 18 additions & 6 deletions packages/runtime-core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ import {
initProps,
normalizePropsOptions
} from './componentProps'
import { Slots, initSlots, InternalSlots } from './componentSlots'
import {
Slots,
initSlots,
InternalSlots,
SlotsType,
TypedSlots
} from './componentSlots'
import { warn } from './warning'
import { ErrorCodes, callWithErrorHandling, handleError } from './errorHandling'
import { AppContext, createAppContext, AppConfig } from './apiCreateApp'
Expand Down Expand Up @@ -117,10 +123,13 @@ export interface ComponentInternalOptions {
__name?: string
}

export interface FunctionalComponent<P = {}, E extends EmitsOptions = {}>
extends ComponentInternalOptions {
export interface FunctionalComponent<
P = {},
E extends EmitsOptions = {},
S extends SlotsType = {}
> extends ComponentInternalOptions {
// use of any here is intentional so it can be a valid JSX Element constructor
(props: P, ctx: Omit<SetupContext<E>, 'expose'>): any
(props: P, ctx: Omit<SetupContext<E, S>, 'expose'>): any
props?: ComponentPropsOptions<P>
emits?: E | (keyof E)[]
inheritAttrs?: boolean
Expand Down Expand Up @@ -168,10 +177,13 @@ export type { ComponentOptions }
type LifecycleHook<TFn = Function> = TFn[] | null

// use `E extends any` to force evaluating type to fix #2362
export type SetupContext<E = EmitsOptions> = E extends any
export type SetupContext<
E = EmitsOptions,
S extends SlotsType = {}
> = E extends any
? {
attrs: Data
slots: Slots
slots: [keyof S] extends [never] ? Slots : TypedSlots<S>
emit: EmitFn<E>
expose: (exposed?: Record<string, any>) => void
}
Expand Down
Loading

0 comments on commit af4880d

Please sign in to comment.