Skip to content

Commit bfb8565

Browse files
authored
feat(types): provide ComponentInstance type (#5408)
1 parent 44135dc commit bfb8565

File tree

3 files changed

+174
-1
lines changed

3 files changed

+174
-1
lines changed
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import {
2+
defineComponent,
3+
FunctionalComponent,
4+
ComponentPublicInstance,
5+
ComponentInstance,
6+
ref
7+
} from 'vue'
8+
import { expectType, describe } from './utils'
9+
10+
describe('defineComponent', () => {
11+
const CompSetup = defineComponent({
12+
props: {
13+
test: String
14+
},
15+
setup() {
16+
return {
17+
a: 1
18+
}
19+
}
20+
})
21+
const compSetup: ComponentInstance<typeof CompSetup> = {} as any
22+
23+
expectType<string | undefined>(compSetup.test)
24+
expectType<number>(compSetup.a)
25+
expectType<ComponentPublicInstance>(compSetup)
26+
})
27+
describe('functional component', () => {
28+
// Functional
29+
const CompFunctional: FunctionalComponent<{ test?: string }> = {} as any
30+
const compFunctional: ComponentInstance<typeof CompFunctional> = {} as any
31+
32+
expectType<string | undefined>(compFunctional.test)
33+
expectType<ComponentPublicInstance>(compFunctional)
34+
35+
const CompFunction: (props: { test?: string }) => any = {} as any
36+
const compFunction: ComponentInstance<typeof CompFunction> = {} as any
37+
38+
expectType<string | undefined>(compFunction.test)
39+
expectType<ComponentPublicInstance>(compFunction)
40+
})
41+
42+
describe('options component', () => {
43+
// Options
44+
const CompOptions = defineComponent({
45+
props: {
46+
test: String
47+
},
48+
data() {
49+
return {
50+
a: 1
51+
}
52+
},
53+
computed: {
54+
b() {
55+
return 'test'
56+
}
57+
},
58+
methods: {
59+
func(a: string) {
60+
return true
61+
}
62+
}
63+
})
64+
const compOptions: ComponentInstance<typeof CompOptions> = {} as any
65+
expectType<string | undefined>(compOptions.test)
66+
expectType<number>(compOptions.a)
67+
expectType<(a: string) => boolean>(compOptions.func)
68+
expectType<ComponentPublicInstance>(compOptions)
69+
})
70+
71+
describe('object no defineComponent', () => {
72+
// object - no defineComponent
73+
74+
const CompObjectSetup = {
75+
props: {
76+
test: String
77+
},
78+
setup() {
79+
return {
80+
a: 1
81+
}
82+
}
83+
}
84+
const compObjectSetup: ComponentInstance<typeof CompObjectSetup> = {} as any
85+
expectType<string | undefined>(compObjectSetup.test)
86+
expectType<number>(compObjectSetup.a)
87+
expectType<ComponentPublicInstance>(compObjectSetup)
88+
89+
const CompObjectData = {
90+
props: {
91+
test: String
92+
},
93+
data() {
94+
return {
95+
a: 1
96+
}
97+
}
98+
}
99+
const compObjectData: ComponentInstance<typeof CompObjectData> = {} as any
100+
expectType<string | undefined>(compObjectData.test)
101+
expectType<number>(compObjectData.a)
102+
expectType<ComponentPublicInstance>(compObjectData)
103+
104+
const CompObjectNoProps = {
105+
data() {
106+
return {
107+
a: 1
108+
}
109+
}
110+
}
111+
const compObjectNoProps: ComponentInstance<typeof CompObjectNoProps> =
112+
{} as any
113+
expectType<string | undefined>(compObjectNoProps.test)
114+
expectType<number>(compObjectNoProps.a)
115+
expectType<ComponentPublicInstance>(compObjectNoProps)
116+
})
117+
118+
describe('Generic component', () => {
119+
const Comp = defineComponent(
120+
// TODO: babel plugin to auto infer runtime props options from type
121+
// similar to defineProps<{...}>()
122+
<T extends string | number>(props: { msg: T; list: T[] }) => {
123+
// use Composition API here like in <script setup>
124+
const count = ref(0)
125+
126+
return () => (
127+
// return a render function (both JSX and h() works)
128+
<div>
129+
{props.msg} {count.value}
130+
</div>
131+
)
132+
}
133+
)
134+
135+
// defaults to known types since types are resolved on instantiation
136+
const comp: ComponentInstance<typeof Comp> = {} as any
137+
expectType<string | number>(comp.msg)
138+
expectType<Array<string | number>>(comp.list)
139+
})

packages/runtime-core/src/component.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,39 @@ import { LifecycleHooks } from './enums'
8383

8484
export type Data = Record<string, unknown>
8585

86+
/**
87+
* Public utility type for extracting the instance type of a component.
88+
* Works with all valid component definition types. This is intended to replace
89+
* the usage of `InstanceType<typeof Comp>` which only works for
90+
* constructor-based component definition types.
91+
*
92+
* Exmaple:
93+
* ```ts
94+
* const MyComp = { ... }
95+
* declare const instance: ComponentInstance<typeof MyComp>
96+
* ```
97+
*/
98+
export type ComponentInstance<T> = T extends { new (): ComponentPublicInstance }
99+
? InstanceType<T>
100+
: T extends FunctionalComponent<infer Props, infer Emits>
101+
? ComponentPublicInstance<Props, {}, {}, {}, {}, Emits>
102+
: T extends Component<
103+
infer Props,
104+
infer RawBindings,
105+
infer D,
106+
infer C,
107+
infer M
108+
>
109+
? // NOTE we override Props/RawBindings/D to make sure is not `unknown`
110+
ComponentPublicInstance<
111+
unknown extends Props ? {} : Props,
112+
unknown extends RawBindings ? {} : RawBindings,
113+
unknown extends D ? {} : D,
114+
C,
115+
M
116+
>
117+
: never // not a vue Component
118+
86119
/**
87120
* For extending allowed non-declared props on components in TSX
88121
*/

packages/runtime-core/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,8 @@ export type {
230230
ComponentInternalInstance,
231231
SetupContext,
232232
ComponentCustomProps,
233-
AllowedComponentProps
233+
AllowedComponentProps,
234+
ComponentInstance
234235
} from './component'
235236
export type { DefineComponent, PublicProps } from './apiDefineComponent'
236237
export type {

0 commit comments

Comments
 (0)