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`] = `
"import { defineComponent as _defineComponent } from 'vue'
export interface Foo {}

View File

@ -1585,6 +1585,45 @@ const emit = defineEmits(['a', 'b'])
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', () => {
const { content, bindings } = compile(
`<script setup lang="ts">

View File

@ -67,6 +67,7 @@ const DEFINE_EMITS = 'defineEmits'
const DEFINE_EXPOSE = 'defineExpose'
const WITH_DEFAULTS = 'withDefaults'
const DEFINE_OPTIONS = 'defineOptions'
const DEFINE_SLOTS = 'defineSlots'
const isBuiltInDir = makeMap(
`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 hasDefaultExportRender = false
let hasDefineOptionsCall = false
let hasDefineSlotsCall = false
let propsRuntimeDecl: Node | undefined
let propsRuntimeDefaults: Node | undefined
let propsDestructureDecl: Node | undefined
@ -590,6 +592,30 @@ export function compileScript(
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[] {
return scriptAst
? [...scriptSetupAst.body, ...scriptAst.body]
@ -683,6 +709,7 @@ export function compileScript(
let propsOption = undefined
let emitsOption = undefined
let exposeOption = undefined
let slotsOption = undefined
if (optionsRuntimeDecl.type === 'ObjectExpression') {
for (const prop of optionsRuntimeDecl.properties) {
if (
@ -692,6 +719,7 @@ export function compileScript(
if (prop.key.name === 'props') propsOption = prop
if (prop.key.name === 'emits') emitsOption = prop
if (prop.key.name === 'expose') exposeOption = prop
if (prop.key.name === 'slots') slotsOption = prop
}
}
}
@ -714,6 +742,12 @@ export function compileScript(
exposeOption
)
}
if (slotsOption) {
error(
`${DEFINE_OPTIONS}() cannot be used to declare slots. Use ${DEFINE_SLOTS}() instead.`,
slotsOption
)
}
return true
}
@ -1286,7 +1320,8 @@ export function compileScript(
processDefineProps(expr) ||
processDefineEmits(expr) ||
processDefineOptions(expr) ||
processWithDefaults(expr)
processWithDefaults(expr) ||
processDefineSlots(expr)
) {
s.remove(node.start! + startOffset, node.end! + startOffset)
} else if (processDefineExpose(expr)) {
@ -1320,7 +1355,10 @@ export function compileScript(
const isDefineProps =
processDefineProps(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 (left === 1) {
s.remove(node.start! + startOffset, node.end! + startOffset)

View File

@ -8,7 +8,10 @@ import {
ComponentPublicInstance,
ComponentOptions,
SetupContext,
h
h,
SlotsType,
Slots,
VNode
} from 'vue'
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 {
DefineComponent,
ComponentOptionsMixin,
@ -1428,6 +1494,7 @@ declare const MyButton: DefineComponent<
ComponentOptionsMixin,
EmitsOptions,
string,
{},
VNodeProps & AllowedComponentProps & ComponentCustomProps,
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'
// simple function signature
@ -68,3 +68,29 @@ const Qux: FunctionalComponent<{}, ['foo', 'bar']> = (props, { emit }) => {
}
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,
useSlots,
withDefaults,
Slots
Slots,
defineSlots,
VNode
} from 'vue'
import { describe, expectType } from './utils'
@ -179,6 +181,27 @@ describe('defineEmits w/ runtime declaration', () => {
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', () => {
const attrs = useAttrs()
expectType<Record<string, unknown>>(attrs)

View File

@ -28,6 +28,7 @@ import {
CreateComponentPublicInstance,
ComponentPublicInstanceConstructor
} from './componentPublicInstance'
import { SlotsType } from './componentSlots'
export type PublicProps = VNodeProps &
AllowedComponentProps &
@ -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
@ -61,6 +63,7 @@ export type DefineComponent<
Mixin,
Extends,
E,
S,
PP & Props,
Defaults,
true
@ -77,6 +80,7 @@ export type DefineComponent<
Extends,
E,
EE,
S,
Defaults
> &
PP
@ -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
@ -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
>(
@ -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 }
@ -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
>(
@ -174,6 +185,7 @@ export function defineComponent<
Extends,
E,
EE,
S,
I,
II
>
@ -186,7 +198,8 @@ export function defineComponent<
Mixin,
Extends,
E,
EE
EE,
S
>
// overload 4: object format with object props declaration
@ -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
>(
@ -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(

View File

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

View File

@ -27,7 +27,13 @@ import {
initProps,
normalizePropsOptions
} from './componentProps'
import { Slots, initSlots, InternalSlots } from './componentSlots'
import {
initSlots,
InternalSlots,
Slots,
SlotsType,
TypedSlots
} from './componentSlots'
import { warn } from './warning'
import { ErrorCodes, callWithErrorHandling, handleError } from './errorHandling'
import { AppContext, createAppContext, AppConfig } from './apiCreateApp'
@ -57,7 +63,8 @@ import {
isPromise,
ShapeFlags,
extend,
getGlobalThis
getGlobalThis,
IfAny
} from '@vue/shared'
import { SuspenseBoundary } from './components/Suspense'
import { CompilerOptions } from '@vue/compiler-core'
@ -117,12 +124,19 @@ export interface ComponentInternalOptions {
__name?: string
}
export interface FunctionalComponent<P = {}, E extends EmitsOptions = {}>
extends ComponentInternalOptions {
export interface FunctionalComponent<
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
(props: P, ctx: Omit<SetupContext<E>, 'expose'>): any
(
props: P,
ctx: Omit<SetupContext<E, IfAny<S, {}, SlotsType<S>>>, 'expose'>
): any
props?: ComponentPropsOptions<P>
emits?: E | (keyof E)[]
slots?: IfAny<S, Slots, SlotsType<S>>
inheritAttrs?: boolean
displayName?: string
compatConfig?: CompatConfig
@ -147,7 +161,7 @@ export type ConcreteComponent<
M extends MethodOptions = MethodOptions
> =
| 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.
@ -168,10 +182,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: TypedSlots<S>
emit: EmitFn<E>
expose: (exposed?: Record<string, any>) => void
}

View File

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

View File

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

View File

@ -13,7 +13,9 @@ import {
ShapeFlags,
extend,
def,
SlotFlags
SlotFlags,
Prettify,
IfAny
} from '@vue/shared'
import { warn } from './warning'
import { isKeepAlive } from './components/KeepAlive'
@ -22,7 +24,9 @@ import { isHmrUpdating } from './hmr'
import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'
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 = {
[name: string]: Slot | undefined
@ -30,6 +34,24 @@ export type 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 = {
[name: string]: unknown
// manual render fn hint to skip forced children updates

View File

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

View File

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

View File

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

View File

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