feat(types/slots): support slot presence / props type checks via `defineSlots` macro and `slots` option (#7982)

This commit is contained in:
三咲智子 Kevin Deng 2023-04-03 16:49:16 +08:00 committed by GitHub
parent 59e828448e
commit 5a2f5d59cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 380 additions and 39 deletions

View File

@ -1785,6 +1785,51 @@ return { props, emit }
})" })"
`; `;
exports[`SFC compile <script setup> > with TypeScript > defineSlots() > basic usage 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`] = ` exports[`SFC compile <script setup> > with TypeScript > hoist type declarations 1`] = `
"import { defineComponent as _defineComponent } from 'vue' "import { defineComponent as _defineComponent } from 'vue'
export interface Foo {} export interface Foo {}

View File

@ -1585,6 +1585,45 @@ const emit = defineEmits(['a', 'b'])
assertCode(content) assertCode(content)
}) })
describe('defineSlots()', () => {
test('basic usage', () => {
const { content } = compile(`
<script setup lang="ts">
const slots = defineSlots<{
default: { msg: string }
}>()
</script>
`)
assertCode(content)
expect(content).toMatch(`const slots = _useSlots()`)
expect(content).not.toMatch('defineSlots')
})
test('w/o return value', () => {
const { content } = compile(`
<script setup lang="ts">
defineSlots<{
default: { msg: string }
}>()
</script>
`)
assertCode(content)
expect(content).not.toMatch('defineSlots')
expect(content).not.toMatch(`_useSlots`)
})
test('w/o generic params', () => {
const { content } = compile(`
<script setup>
const slots = defineSlots()
</script>
`)
assertCode(content)
expect(content).toMatch(`const slots = _useSlots()`)
expect(content).not.toMatch('defineSlots')
})
})
test('runtime Enum', () => { test('runtime Enum', () => {
const { content, bindings } = compile( const { content, bindings } = compile(
`<script setup lang="ts"> `<script setup lang="ts">

View File

@ -67,6 +67,7 @@ const DEFINE_EMITS = 'defineEmits'
const DEFINE_EXPOSE = 'defineExpose' const DEFINE_EXPOSE = 'defineExpose'
const WITH_DEFAULTS = 'withDefaults' const WITH_DEFAULTS = 'withDefaults'
const DEFINE_OPTIONS = 'defineOptions' const DEFINE_OPTIONS = 'defineOptions'
const DEFINE_SLOTS = 'defineSlots'
const isBuiltInDir = makeMap( const isBuiltInDir = makeMap(
`once,memo,if,for,else,else-if,slot,text,html,on,bind,model,show,cloak,is` `once,memo,if,for,else,else-if,slot,text,html,on,bind,model,show,cloak,is`
@ -312,6 +313,7 @@ export function compileScript(
let hasDefaultExportName = false let hasDefaultExportName = false
let hasDefaultExportRender = false let hasDefaultExportRender = false
let hasDefineOptionsCall = false let hasDefineOptionsCall = false
let hasDefineSlotsCall = false
let propsRuntimeDecl: Node | undefined let propsRuntimeDecl: Node | undefined
let propsRuntimeDefaults: Node | undefined let propsRuntimeDefaults: Node | undefined
let propsDestructureDecl: Node | undefined let propsDestructureDecl: Node | undefined
@ -590,6 +592,30 @@ export function compileScript(
return true return true
} }
function processDefineSlots(node: Node, declId?: LVal): boolean {
if (!isCallOf(node, DEFINE_SLOTS)) {
return false
}
if (hasDefineSlotsCall) {
error(`duplicate ${DEFINE_SLOTS}() call`, node)
}
hasDefineSlotsCall = true
if (node.arguments.length > 0) {
error(`${DEFINE_SLOTS}() cannot accept arguments`, node)
}
if (declId) {
s.overwrite(
startOffset + node.start!,
startOffset + node.end!,
`${helper('useSlots')}()`
)
}
return true
}
function getAstBody(): Statement[] { function getAstBody(): Statement[] {
return scriptAst return scriptAst
? [...scriptSetupAst.body, ...scriptAst.body] ? [...scriptSetupAst.body, ...scriptAst.body]
@ -683,6 +709,7 @@ export function compileScript(
let propsOption = undefined let propsOption = undefined
let emitsOption = undefined let emitsOption = undefined
let exposeOption = undefined let exposeOption = undefined
let slotsOption = undefined
if (optionsRuntimeDecl.type === 'ObjectExpression') { if (optionsRuntimeDecl.type === 'ObjectExpression') {
for (const prop of optionsRuntimeDecl.properties) { for (const prop of optionsRuntimeDecl.properties) {
if ( if (
@ -692,6 +719,7 @@ export function compileScript(
if (prop.key.name === 'props') propsOption = prop if (prop.key.name === 'props') propsOption = prop
if (prop.key.name === 'emits') emitsOption = prop if (prop.key.name === 'emits') emitsOption = prop
if (prop.key.name === 'expose') exposeOption = prop if (prop.key.name === 'expose') exposeOption = prop
if (prop.key.name === 'slots') slotsOption = prop
} }
} }
} }
@ -714,6 +742,12 @@ export function compileScript(
exposeOption exposeOption
) )
} }
if (slotsOption) {
error(
`${DEFINE_OPTIONS}() cannot be used to declare slots. Use ${DEFINE_SLOTS}() instead.`,
slotsOption
)
}
return true return true
} }
@ -1286,7 +1320,8 @@ export function compileScript(
processDefineProps(expr) || processDefineProps(expr) ||
processDefineEmits(expr) || processDefineEmits(expr) ||
processDefineOptions(expr) || processDefineOptions(expr) ||
processWithDefaults(expr) processWithDefaults(expr) ||
processDefineSlots(expr)
) { ) {
s.remove(node.start! + startOffset, node.end! + startOffset) s.remove(node.start! + startOffset, node.end! + startOffset)
} else if (processDefineExpose(expr)) { } else if (processDefineExpose(expr)) {
@ -1320,7 +1355,10 @@ export function compileScript(
const isDefineProps = const isDefineProps =
processDefineProps(init, decl.id) || processDefineProps(init, decl.id) ||
processWithDefaults(init, decl.id) processWithDefaults(init, decl.id)
const isDefineEmits = processDefineEmits(init, decl.id) const isDefineEmits =
!isDefineProps && processDefineEmits(init, decl.id)
!isDefineEmits && processDefineSlots(init, decl.id)
if (isDefineProps || isDefineEmits) { if (isDefineProps || isDefineEmits) {
if (left === 1) { if (left === 1) {
s.remove(node.start! + startOffset, node.end! + startOffset) s.remove(node.start! + startOffset, node.end! + startOffset)

View File

@ -8,7 +8,10 @@ import {
ComponentPublicInstance, ComponentPublicInstance,
ComponentOptions, ComponentOptions,
SetupContext, SetupContext,
h h,
SlotsType,
Slots,
VNode
} from 'vue' } from 'vue'
import { describe, expectType, IsUnion } from './utils' import { describe, expectType, IsUnion } from './utils'
@ -1406,6 +1409,69 @@ export default {
}) })
} }
describe('slots', () => {
const comp1 = defineComponent({
slots: Object as SlotsType<{
default: { foo: string; bar: number }
optional?: { data: string }
undefinedScope: undefined | { data: string }
optionalUndefinedScope?: undefined | { data: string }
}>,
setup(props, { slots }) {
expectType<(scope: { foo: string; bar: number }) => VNode[]>(
slots.default
)
expectType<((scope: { data: string }) => VNode[]) | undefined>(
slots.optional
)
slots.default({ foo: 'foo', bar: 1 })
// @ts-expect-error it's optional
slots.optional({ data: 'foo' })
slots.optional?.({ data: 'foo' })
expectType<{
(): VNode[]
(scope: undefined | { data: string }): VNode[]
}>(slots.undefinedScope)
expectType<
| { (): VNode[]; (scope: undefined | { data: string }): VNode[] }
| undefined
>(slots.optionalUndefinedScope)
slots.default({ foo: 'foo', bar: 1 })
// @ts-expect-error it's optional
slots.optional({ data: 'foo' })
slots.optional?.({ data: 'foo' })
slots.undefinedScope()
slots.undefinedScope(undefined)
// @ts-expect-error
slots.undefinedScope('foo')
slots.optionalUndefinedScope?.()
slots.optionalUndefinedScope?.(undefined)
slots.optionalUndefinedScope?.({ data: 'foo' })
// @ts-expect-error
slots.optionalUndefinedScope()
// @ts-expect-error
slots.optionalUndefinedScope?.('foo')
expectType<typeof slots | undefined>(new comp1().$slots)
}
})
const comp2 = defineComponent({
setup(props, { slots }) {
// unknown slots
expectType<Slots>(slots)
expectType<((...args: any[]) => VNode[]) | undefined>(slots.default)
}
})
expectType<Slots | undefined>(new comp2().$slots)
})
import { import {
DefineComponent, DefineComponent,
ComponentOptionsMixin, ComponentOptionsMixin,
@ -1428,6 +1494,7 @@ declare const MyButton: DefineComponent<
ComponentOptionsMixin, ComponentOptionsMixin,
EmitsOptions, EmitsOptions,
string, string,
{},
VNodeProps & AllowedComponentProps & ComponentCustomProps, VNodeProps & AllowedComponentProps & ComponentCustomProps,
Readonly<ExtractPropTypes<{}>>, Readonly<ExtractPropTypes<{}>>,
{} {}

View File

@ -1,4 +1,4 @@
import { h, Text, FunctionalComponent, Component } from 'vue' import { h, Text, FunctionalComponent, Component, VNode } from 'vue'
import { expectType } from './utils' import { expectType } from './utils'
// simple function signature // simple function signature
@ -68,3 +68,29 @@ const Qux: FunctionalComponent<{}, ['foo', 'bar']> = (props, { emit }) => {
} }
expectType<Component>(Qux) expectType<Component>(Qux)
const Quux: FunctionalComponent<
{},
{},
{
default: { foo: number }
optional?: { foo: number }
}
> = (props, { emit, slots }) => {
expectType<{
default: (scope: { foo: number }) => VNode[]
optional?: (scope: { foo: number }) => VNode[]
}>(slots)
slots.default({ foo: 123 })
// @ts-expect-error
slots.default({ foo: 'fesf' })
slots.optional?.({ foo: 123 })
// @ts-expect-error
slots.optional?.({ foo: 'fesf' })
// @ts-expect-error
slots.optional({ foo: 123 })
}
expectType<Component>(Quux)
;<Quux />

View File

@ -4,7 +4,9 @@ import {
useAttrs, useAttrs,
useSlots, useSlots,
withDefaults, withDefaults,
Slots Slots,
defineSlots,
VNode
} from 'vue' } from 'vue'
import { describe, expectType } from './utils' import { describe, expectType } from './utils'
@ -179,6 +181,27 @@ describe('defineEmits w/ runtime declaration', () => {
emit2('baz') emit2('baz')
}) })
describe('defineSlots', () => {
// short syntax
const slots = defineSlots<{
default: { foo: string; bar: number }
optional?: string
}>()
expectType<(scope: { foo: string; bar: number }) => VNode[]>(slots.default)
expectType<undefined | ((scope: string) => VNode[])>(slots.optional)
// literal fn syntax (allow for specifying return type)
const fnSlots = defineSlots<{
default(props: { foo: string; bar: number }): any
optional?(props: string): any
}>()
expectType<(scope: { foo: string; bar: number }) => VNode[]>(fnSlots.default)
expectType<undefined | ((scope: string) => VNode[])>(fnSlots.optional)
const slotsUntype = defineSlots()
expectType<Slots>(slotsUntype)
})
describe('useAttrs', () => { describe('useAttrs', () => {
const attrs = useAttrs() const attrs = useAttrs()
expectType<Record<string, unknown>>(attrs) expectType<Record<string, unknown>>(attrs)

View File

@ -28,6 +28,7 @@ import {
CreateComponentPublicInstance, CreateComponentPublicInstance,
ComponentPublicInstanceConstructor ComponentPublicInstanceConstructor
} from './componentPublicInstance' } from './componentPublicInstance'
import { SlotsType } from './componentSlots'
export type PublicProps = VNodeProps & export type PublicProps = VNodeProps &
AllowedComponentProps & AllowedComponentProps &
@ -43,6 +44,7 @@ export type DefineComponent<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {}, E extends EmitsOptions = {},
EE extends string = string, EE extends string = string,
S extends SlotsType = {},
PP = PublicProps, PP = PublicProps,
Props = Readonly< Props = Readonly<
PropsOrPropOptions extends ComponentPropsOptions PropsOrPropOptions extends ComponentPropsOptions
@ -61,6 +63,7 @@ export type DefineComponent<
Mixin, Mixin,
Extends, Extends,
E, E,
S,
PP & Props, PP & Props,
Defaults, Defaults,
true true
@ -77,6 +80,7 @@ export type DefineComponent<
Extends, Extends,
E, E,
EE, EE,
S,
Defaults Defaults
> & > &
PP PP
@ -91,29 +95,33 @@ export type DefineComponent<
export function defineComponent< export function defineComponent<
Props extends Record<string, any>, Props extends Record<string, any>,
E extends EmitsOptions = {}, E extends EmitsOptions = {},
EE extends string = string EE extends string = string,
S extends SlotsType = {}
>( >(
setup: ( setup: (
props: Props, props: Props,
ctx: SetupContext<E> ctx: SetupContext<E, S>
) => RenderFunction | Promise<RenderFunction>, ) => RenderFunction | Promise<RenderFunction>,
options?: Pick<ComponentOptions, 'name' | 'inheritAttrs'> & { options?: Pick<ComponentOptions, 'name' | 'inheritAttrs'> & {
props?: (keyof Props)[] props?: (keyof Props)[]
emits?: E | EE[] emits?: E | EE[]
slots?: S
} }
): (props: Props & EmitsToProps<E>) => any ): (props: Props & EmitsToProps<E>) => any
export function defineComponent< export function defineComponent<
Props extends Record<string, any>, Props extends Record<string, any>,
E extends EmitsOptions = {}, E extends EmitsOptions = {},
EE extends string = string EE extends string = string,
S extends SlotsType = {}
>( >(
setup: ( setup: (
props: Props, props: Props,
ctx: SetupContext<E> ctx: SetupContext<E, S>
) => RenderFunction | Promise<RenderFunction>, ) => RenderFunction | Promise<RenderFunction>,
options?: Pick<ComponentOptions, 'name' | 'inheritAttrs'> & { options?: Pick<ComponentOptions, 'name' | 'inheritAttrs'> & {
props?: ComponentObjectPropsOptions<Props> props?: ComponentObjectPropsOptions<Props>
emits?: E | EE[] emits?: E | EE[]
slots?: S
} }
): (props: Props & EmitsToProps<E>) => any ): (props: Props & EmitsToProps<E>) => any
@ -130,6 +138,7 @@ export function defineComponent<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {}, E extends EmitsOptions = {},
EE extends string = string, EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
II extends string = string II extends string = string
>( >(
@ -143,10 +152,11 @@ export function defineComponent<
Extends, Extends,
E, E,
EE, EE,
S,
I, I,
II 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 // overload 3: object format with array props declaration
// props inferred as { [key in PropNames]?: any } // props inferred as { [key in PropNames]?: any }
@ -161,6 +171,7 @@ export function defineComponent<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {}, E extends EmitsOptions = {},
EE extends string = string, EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
II extends string = string II extends string = string
>( >(
@ -174,6 +185,7 @@ export function defineComponent<
Extends, Extends,
E, E,
EE, EE,
S,
I, I,
II II
> >
@ -186,7 +198,8 @@ export function defineComponent<
Mixin, Mixin,
Extends, Extends,
E, E,
EE EE,
S
> >
// overload 4: object format with object props declaration // overload 4: object format with object props declaration
@ -203,6 +216,7 @@ export function defineComponent<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {}, E extends EmitsOptions = {},
EE extends string = string, EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
II extends string = string II extends string = string
>( >(
@ -216,10 +230,11 @@ export function defineComponent<
Extends, Extends,
E, E,
EE, EE,
S,
I, I,
II 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 // implementation, close to no-op
export function defineComponent( export function defineComponent(

View File

@ -25,6 +25,7 @@ import {
ExtractPropTypes ExtractPropTypes
} from './componentProps' } from './componentProps'
import { warn } from './warning' import { warn } from './warning'
import { SlotsType, TypedSlots } from './componentSlots'
// dev only // dev only
const warnRuntimeUsage = (method: string) => const warnRuntimeUsage = (method: string) =>
@ -184,9 +185,7 @@ export function defineOptions<
C extends ComputedOptions = {}, C extends ComputedOptions = {},
M extends MethodOptions = {}, M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin
E extends EmitsOptions = EmitsOptions,
EE extends string = string
>( >(
options?: ComponentOptionsWithoutProps< options?: ComponentOptionsWithoutProps<
{}, {},
@ -195,16 +194,23 @@ export function defineOptions<
C, C,
M, M,
Mixin, Mixin,
Extends, Extends
E, > & { emits?: undefined; expose?: undefined; slots?: undefined }
EE
> & { emits?: undefined; expose?: undefined }
): void { ): void {
if (__DEV__) { if (__DEV__) {
warnRuntimeUsage(`defineOptions`) warnRuntimeUsage(`defineOptions`)
} }
} }
export function defineSlots<
S extends Record<string, any> = Record<string, any>
>(): // @ts-expect-error
TypedSlots<SlotsType<S>> {
if (__DEV__) {
warnRuntimeUsage(`defineSlots`)
}
}
type NotUndefined<T> = T extends undefined ? never : T type NotUndefined<T> = T extends undefined ? never : T
type InferDefaults<T> = { type InferDefaults<T> = {

View File

@ -27,7 +27,13 @@ import {
initProps, initProps,
normalizePropsOptions normalizePropsOptions
} from './componentProps' } from './componentProps'
import { Slots, initSlots, InternalSlots } from './componentSlots' import {
initSlots,
InternalSlots,
Slots,
SlotsType,
TypedSlots
} from './componentSlots'
import { warn } from './warning' import { warn } from './warning'
import { ErrorCodes, callWithErrorHandling, handleError } from './errorHandling' import { ErrorCodes, callWithErrorHandling, handleError } from './errorHandling'
import { AppContext, createAppContext, AppConfig } from './apiCreateApp' import { AppContext, createAppContext, AppConfig } from './apiCreateApp'
@ -57,7 +63,8 @@ import {
isPromise, isPromise,
ShapeFlags, ShapeFlags,
extend, extend,
getGlobalThis getGlobalThis,
IfAny
} from '@vue/shared' } from '@vue/shared'
import { SuspenseBoundary } from './components/Suspense' import { SuspenseBoundary } from './components/Suspense'
import { CompilerOptions } from '@vue/compiler-core' import { CompilerOptions } from '@vue/compiler-core'
@ -117,12 +124,19 @@ export interface ComponentInternalOptions {
__name?: string __name?: string
} }
export interface FunctionalComponent<P = {}, E extends EmitsOptions = {}> export interface FunctionalComponent<
extends ComponentInternalOptions { P = {},
E extends EmitsOptions = {},
S extends Record<string, any> = any
> extends ComponentInternalOptions {
// use of any here is intentional so it can be a valid JSX Element constructor // 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, IfAny<S, {}, SlotsType<S>>>, 'expose'>
): any
props?: ComponentPropsOptions<P> props?: ComponentPropsOptions<P>
emits?: E | (keyof E)[] emits?: E | (keyof E)[]
slots?: IfAny<S, Slots, SlotsType<S>>
inheritAttrs?: boolean inheritAttrs?: boolean
displayName?: string displayName?: string
compatConfig?: CompatConfig compatConfig?: CompatConfig
@ -147,7 +161,7 @@ export type ConcreteComponent<
M extends MethodOptions = MethodOptions M extends MethodOptions = MethodOptions
> = > =
| ComponentOptions<Props, RawBindings, D, C, M> | ComponentOptions<Props, RawBindings, D, C, M>
| FunctionalComponent<Props, any> | FunctionalComponent<Props, any, any>
/** /**
* A type used in public APIs where a component type is expected. * A type used in public APIs where a component type is expected.
@ -168,10 +182,13 @@ export type { ComponentOptions }
type LifecycleHook<TFn = Function> = TFn[] | null type LifecycleHook<TFn = Function> = TFn[] | null
// use `E extends any` to force evaluating type to fix #2362 // 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 attrs: Data
slots: Slots slots: TypedSlots<S>
emit: EmitFn<E> emit: EmitFn<E>
expose: (exposed?: Record<string, any>) => void expose: (exposed?: Record<string, any>) => void
} }

View File

@ -74,6 +74,7 @@ import {
} from './compat/compatConfig' } from './compat/compatConfig'
import { OptionMergeFunction } from './apiCreateApp' import { OptionMergeFunction } from './apiCreateApp'
import { LifecycleHooks } from './enums' import { LifecycleHooks } from './enums'
import { SlotsType } from './componentSlots'
/** /**
* Interface for declaring custom options. * Interface for declaring custom options.
@ -105,6 +106,7 @@ export interface ComponentOptionsBase<
Extends extends ComponentOptionsMixin, Extends extends ComponentOptionsMixin,
E extends EmitsOptions, E extends EmitsOptions,
EE extends string = string, EE extends string = string,
S extends SlotsType = {},
Defaults = {}, Defaults = {},
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
II extends string = string II extends string = string
@ -122,7 +124,7 @@ export interface ComponentOptionsBase<
> >
> >
>, >,
ctx: SetupContext<E> ctx: SetupContext<E, S>
) => Promise<RawBindings> | RawBindings | RenderFunction | void ) => Promise<RawBindings> | RawBindings | RenderFunction | void
name?: string name?: string
template?: string | object // can be a direct DOM node template?: string | object // can be a direct DOM node
@ -136,6 +138,7 @@ export interface ComponentOptionsBase<
directives?: Record<string, Directive> directives?: Record<string, Directive>
inheritAttrs?: boolean inheritAttrs?: boolean
emits?: (E | EE[]) & ThisType<void> emits?: (E | EE[]) & ThisType<void>
slots?: S
// TODO infer public instance type based on exposed keys // TODO infer public instance type based on exposed keys
expose?: string[] expose?: string[]
serverPrefetch?(): void | Promise<any> serverPrefetch?(): void | Promise<any>
@ -216,6 +219,7 @@ export type ComponentOptionsWithoutProps<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions, E extends EmitsOptions = EmitsOptions,
EE extends string = string, EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
II extends string = string, II extends string = string,
PE = Props & EmitsToProps<E> PE = Props & EmitsToProps<E>
@ -229,6 +233,7 @@ export type ComponentOptionsWithoutProps<
Extends, Extends,
E, E,
EE, EE,
S,
{}, {},
I, I,
II II
@ -244,6 +249,7 @@ export type ComponentOptionsWithoutProps<
Mixin, Mixin,
Extends, Extends,
E, E,
S,
PE, PE,
{}, {},
false, false,
@ -261,6 +267,7 @@ export type ComponentOptionsWithArrayProps<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions, E extends EmitsOptions = EmitsOptions,
EE extends string = string, EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
II extends string = string, II extends string = string,
Props = Prettify<Readonly<{ [key in PropNames]?: any } & EmitsToProps<E>>> Props = Prettify<Readonly<{ [key in PropNames]?: any } & EmitsToProps<E>>>
@ -274,6 +281,7 @@ export type ComponentOptionsWithArrayProps<
Extends, Extends,
E, E,
EE, EE,
S,
{}, {},
I, I,
II II
@ -289,6 +297,7 @@ export type ComponentOptionsWithArrayProps<
Mixin, Mixin,
Extends, Extends,
E, E,
S,
Props, Props,
{}, {},
false, false,
@ -306,6 +315,7 @@ export type ComponentOptionsWithObjectProps<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions, E extends EmitsOptions = EmitsOptions,
EE extends string = string, EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
II extends string = string, II extends string = string,
Props = Prettify<Readonly<ExtractPropTypes<PropsOptions> & EmitsToProps<E>>>, Props = Prettify<Readonly<ExtractPropTypes<PropsOptions> & EmitsToProps<E>>>,
@ -320,6 +330,7 @@ export type ComponentOptionsWithObjectProps<
Extends, Extends,
E, E,
EE, EE,
S,
Defaults, Defaults,
I, I,
II II
@ -335,6 +346,7 @@ export type ComponentOptionsWithObjectProps<
Mixin, Mixin,
Extends, Extends,
E, E,
S,
Props, Props,
Defaults, Defaults,
false, false,
@ -350,8 +362,20 @@ export type ComponentOptions<
M extends MethodOptions = any, M extends MethodOptions = any,
Mixin extends ComponentOptionsMixin = any, Mixin extends ComponentOptionsMixin = any,
Extends extends ComponentOptionsMixin = any, Extends extends ComponentOptionsMixin = any,
E extends EmitsOptions = any E extends EmitsOptions = any,
> = ComponentOptionsBase<Props, RawBindings, D, C, M, Mixin, Extends, E> & S extends SlotsType = any
> = ComponentOptionsBase<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
string,
S
> &
ThisType< ThisType<
CreateComponentPublicInstance< CreateComponentPublicInstance<
{}, {},
@ -376,6 +400,7 @@ export type ComponentOptionsMixin = ComponentOptionsBase<
any, any,
any, any,
any, any,
any,
any any
> >

View File

@ -40,7 +40,7 @@ import {
ComponentInjectOptions ComponentInjectOptions
} from './componentOptions' } from './componentOptions'
import { EmitsOptions, EmitFn } from './componentEmits' import { EmitsOptions, EmitFn } from './componentEmits'
import { Slots } from './componentSlots' import { SlotsType, TypedSlots } from './componentSlots'
import { markAttrsAccessed } from './componentRenderUtils' import { markAttrsAccessed } from './componentRenderUtils'
import { currentRenderingInstance } from './componentRenderContext' import { currentRenderingInstance } from './componentRenderContext'
import { warn } from './warning' import { warn } from './warning'
@ -89,6 +89,7 @@ type MixinToOptionTypes<T> = T extends ComponentOptionsBase<
infer Extends, infer Extends,
any, any,
any, any,
any,
infer Defaults infer Defaults
> >
? OptionTypesType<P & {}, B & {}, D & {}, C & {}, M & {}, Defaults & {}> & ? OptionTypesType<P & {}, B & {}, D & {}, C & {}, M & {}, Defaults & {}> &
@ -141,6 +142,7 @@ export type CreateComponentPublicInstance<
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {}, E extends EmitsOptions = {},
S extends SlotsType = {},
PublicProps = P, PublicProps = P,
Defaults = {}, Defaults = {},
MakeDefaultsOptional extends boolean = false, MakeDefaultsOptional extends boolean = false,
@ -162,10 +164,11 @@ export type CreateComponentPublicInstance<
PublicC, PublicC,
PublicM, PublicM,
E, E,
S,
PublicProps, PublicProps,
PublicDefaults, PublicDefaults,
MakeDefaultsOptional, MakeDefaultsOptional,
ComponentOptionsBase<P, B, D, C, M, Mixin, Extends, E, string, Defaults>, ComponentOptionsBase<P, B, D, C, M, Mixin, Extends, E, string, S, Defaults>,
I I
> >
@ -178,6 +181,7 @@ export type ComponentPublicInstance<
C extends ComputedOptions = {}, C extends ComputedOptions = {},
M extends MethodOptions = {}, M extends MethodOptions = {},
E extends EmitsOptions = {}, E extends EmitsOptions = {},
S extends SlotsType = {},
PublicProps = P, PublicProps = P,
Defaults = {}, Defaults = {},
MakeDefaultsOptional extends boolean = false, MakeDefaultsOptional extends boolean = false,
@ -193,7 +197,7 @@ export type ComponentPublicInstance<
> >
$attrs: Data $attrs: Data
$refs: Data $refs: Data
$slots: Slots $slots: TypedSlots<S>
$root: ComponentPublicInstance | null $root: ComponentPublicInstance | null
$parent: ComponentPublicInstance | null $parent: ComponentPublicInstance | null
$emit: EmitFn<E> $emit: EmitFn<E>

View File

@ -13,7 +13,9 @@ import {
ShapeFlags, ShapeFlags,
extend, extend,
def, def,
SlotFlags SlotFlags,
Prettify,
IfAny
} from '@vue/shared' } from '@vue/shared'
import { warn } from './warning' import { warn } from './warning'
import { isKeepAlive } from './components/KeepAlive' import { isKeepAlive } from './components/KeepAlive'
@ -22,7 +24,9 @@ import { isHmrUpdating } from './hmr'
import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig' import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'
import { toRaw } from '@vue/reactivity' import { toRaw } from '@vue/reactivity'
export type Slot = (...args: any[]) => VNode[] export type Slot<T extends any = any> = (
...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>
) => VNode[]
export type InternalSlots = { export type InternalSlots = {
[name: string]: Slot | undefined [name: string]: Slot | undefined
@ -30,6 +34,24 @@ export type InternalSlots = {
export type Slots = Readonly<InternalSlots> export type Slots = Readonly<InternalSlots>
declare const SlotSymbol: unique symbol
export type SlotsType<T extends Record<string, any> = Record<string, any>> = {
[SlotSymbol]?: T
}
export type TypedSlots<
S extends SlotsType,
T = NonNullable<S[typeof SlotSymbol]>
> = [keyof S] extends [never]
? Slots
: Readonly<
Prettify<{
[K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any
? T[K]
: Slot<T[K]>
}>
>
export type RawSlots = { export type RawSlots = {
[name: string]: unknown [name: string]: unknown
// manual render fn hint to skip forced children updates // manual render fn hint to skip forced children updates

View File

@ -120,8 +120,12 @@ export function h(
): VNode ): VNode
// functional component // functional component
export function h<P, E extends EmitsOptions = {}>( export function h<
type: FunctionalComponent<P, E>, P,
E extends EmitsOptions = {},
S extends Record<string, any> = {}
>(
type: FunctionalComponent<P, E, S>,
props?: (RawProps & P) | ({} extends P ? null : never), props?: (RawProps & P) | ({} extends P ? null : never),
children?: RawChildren | RawSlots children?: RawChildren | RawSlots
): VNode ): VNode

View File

@ -70,6 +70,7 @@ export {
defineEmits, defineEmits,
defineExpose, defineExpose,
defineOptions, defineOptions,
defineSlots,
withDefaults, withDefaults,
// internal // internal
mergeDefaults, mergeDefaults,
@ -243,7 +244,7 @@ export type {
RootRenderFunction RootRenderFunction
} from './renderer' } from './renderer'
export type { RootHydrateFunction } from './hydration' export type { RootHydrateFunction } from './hydration'
export type { Slot, Slots } from './componentSlots' export type { Slot, Slots, SlotsType } from './componentSlots'
export type { export type {
Prop, Prop,
PropType, PropType,

View File

@ -4,6 +4,7 @@ type _defineProps = typeof defineProps
type _defineEmits = typeof defineEmits type _defineEmits = typeof defineEmits
type _defineExpose = typeof defineExpose type _defineExpose = typeof defineExpose
type _defineOptions = typeof defineOptions type _defineOptions = typeof defineOptions
type _defineSlots = typeof defineSlots
type _withDefaults = typeof withDefaults type _withDefaults = typeof withDefaults
declare global { declare global {
@ -11,5 +12,6 @@ declare global {
const defineEmits: _defineEmits const defineEmits: _defineEmits
const defineExpose: _defineExpose const defineExpose: _defineExpose
const defineOptions: _defineOptions const defineOptions: _defineOptions
const defineSlots: _defineSlots
const withDefaults: _withDefaults const withDefaults: _withDefaults
} }

View File

@ -20,7 +20,8 @@ import {
warn, warn,
ConcreteComponent, ConcreteComponent,
ComponentOptions, ComponentOptions,
ComponentInjectOptions ComponentInjectOptions,
SlotsType
} from '@vue/runtime-core' } from '@vue/runtime-core'
import { camelize, extend, hyphenate, isArray, toNumber } from '@vue/shared' import { camelize, extend, hyphenate, isArray, toNumber } from '@vue/shared'
import { hydrate, render } from '.' import { hydrate, render } from '.'
@ -51,6 +52,7 @@ export function defineCustomElement<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions, E extends EmitsOptions = EmitsOptions,
EE extends string = string, EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
II extends string = string II extends string = string
>( >(
@ -64,6 +66,7 @@ export function defineCustomElement<
Extends, Extends,
E, E,
EE, EE,
S,
I, I,
II II
> & { styles?: string[] } > & { styles?: string[] }
@ -80,6 +83,7 @@ export function defineCustomElement<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = Record<string, any>, E extends EmitsOptions = Record<string, any>,
EE extends string = string, EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
II extends string = string II extends string = string
>( >(
@ -93,6 +97,7 @@ export function defineCustomElement<
Extends, Extends,
E, E,
EE, EE,
S,
I, I,
II II
> & { styles?: string[] } > & { styles?: string[] }
@ -109,6 +114,7 @@ export function defineCustomElement<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = Record<string, any>, E extends EmitsOptions = Record<string, any>,
EE extends string = string, EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
II extends string = string II extends string = string
>( >(
@ -122,6 +128,7 @@ export function defineCustomElement<
Extends, Extends,
E, E,
EE, EE,
S,
I, I,
II II
> & { styles?: string[] } > & { styles?: string[] }