Skip to content

Commit 9b160b9

Browse files
authored
fix(types): declared prop keys should always exist in props argument (#3726)
1 parent f01aadf commit 9b160b9

File tree

3 files changed

+54
-5
lines changed

3 files changed

+54
-5
lines changed

packages/runtime-core/src/componentOptions.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ import {
6565
import { warn } from './warning'
6666
import { VNodeChild } from './vnode'
6767
import { callWithAsyncErrorHandling } from './errorHandling'
68-
import { UnionToIntersection } from './helpers/typeUtils'
68+
import { LooseRequired, UnionToIntersection } from './helpers/typeUtils'
6969
import { deepMergeData } from './compat/data'
7070
import { DeprecationTypes } from './compat/compatConfig'
7171
import {
@@ -130,9 +130,13 @@ export interface ComponentOptionsBase<
130130
ComponentCustomOptions {
131131
setup?: (
132132
this: void,
133-
props: Props &
134-
UnionToIntersection<ExtractOptionProp<Mixin>> &
135-
UnionToIntersection<ExtractOptionProp<Extends>>,
133+
props: Readonly<
134+
LooseRequired<
135+
Props &
136+
UnionToIntersection<ExtractOptionProp<Mixin>> &
137+
UnionToIntersection<ExtractOptionProp<Extends>>
138+
>
139+
>,
136140
ctx: SetupContext<E>
137141
) => Promise<RawBindings> | RawBindings | RenderFunction | void
138142
name?: string

packages/runtime-core/src/helpers/typeUtils.ts

+3
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ export type UnionToIntersection<U> = (U extends any
33
: never) extends ((k: infer I) => void)
44
? I
55
: never
6+
7+
// make keys required but keep undefined values
8+
export type LooseRequired<T> = { [P in string & keyof T]: T[P] }

test-dts/component.test-d.ts

+43-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
expectType,
1010
ShallowUnwrapRef,
1111
FunctionalComponent,
12-
ComponentPublicInstance
12+
ComponentPublicInstance,
13+
toRefs
1314
} from './index'
1415

1516
declare function extractComponentOptions<Props, RawBindings>(
@@ -42,6 +43,27 @@ describe('object props', () => {
4243
object?: object
4344
}
4445

46+
interface ExpectedRefs {
47+
a: Ref<number | undefined>
48+
b: Ref<string>
49+
e: Ref<Function | undefined>
50+
bb: Ref<string>
51+
bbb: Ref<string>
52+
cc: Ref<string[] | undefined>
53+
dd: Ref<{ n: 1 }>
54+
ee: Ref<(() => string) | undefined>
55+
ff: Ref<((a: number, b: string) => { a: boolean }) | undefined>
56+
ccc: Ref<string[] | undefined>
57+
ddd: Ref<string[]>
58+
eee: Ref<() => { a: string }>
59+
fff: Ref<(a: number, b: string) => { a: boolean }>
60+
hhh: Ref<boolean>
61+
ggg: Ref<'foo' | 'bar'>
62+
ffff: Ref<(a: number, b: string) => { a: boolean }>
63+
validated: Ref<string | undefined>
64+
object: Ref<object | undefined>
65+
}
66+
4567
describe('defineComponent', () => {
4668
const MyComponent = defineComponent({
4769
props: {
@@ -111,6 +133,26 @@ describe('object props', () => {
111133
object: Object as PropType<object>
112134
},
113135
setup(props) {
136+
const refs = toRefs(props)
137+
expectType<ExpectedRefs['a']>(refs.a)
138+
expectType<ExpectedRefs['b']>(refs.b)
139+
expectType<ExpectedRefs['e']>(refs.e)
140+
expectType<ExpectedRefs['bb']>(refs.bb)
141+
expectType<ExpectedRefs['bbb']>(refs.bbb)
142+
expectType<ExpectedRefs['cc']>(refs.cc)
143+
expectType<ExpectedRefs['dd']>(refs.dd)
144+
expectType<ExpectedRefs['ee']>(refs.ee)
145+
expectType<ExpectedRefs['ff']>(refs.ff)
146+
expectType<ExpectedRefs['ccc']>(refs.ccc)
147+
expectType<ExpectedRefs['ddd']>(refs.ddd)
148+
expectType<ExpectedRefs['eee']>(refs.eee)
149+
expectType<ExpectedRefs['fff']>(refs.fff)
150+
expectType<ExpectedRefs['hhh']>(refs.hhh)
151+
expectType<ExpectedRefs['ggg']>(refs.ggg)
152+
expectType<ExpectedRefs['ffff']>(refs.ffff)
153+
expectType<ExpectedRefs['validated']>(refs.validated)
154+
expectType<ExpectedRefs['object']>(refs.object)
155+
114156
return {
115157
setupA: 1,
116158
setupB: ref(1),

0 commit comments

Comments
 (0)