Skip to content

Commit

Permalink
feat(types): provide ComponentInstance type (#5408)
Browse files Browse the repository at this point in the history
  • Loading branch information
pikax committed Dec 8, 2023
1 parent 44135dc commit bfb8565
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 1 deletion.
139 changes: 139 additions & 0 deletions packages/dts-test/componentInstance.test-d.tsx
@@ -0,0 +1,139 @@
import {
defineComponent,
FunctionalComponent,
ComponentPublicInstance,
ComponentInstance,
ref
} from 'vue'
import { expectType, describe } from './utils'

describe('defineComponent', () => {
const CompSetup = defineComponent({
props: {
test: String
},
setup() {
return {
a: 1
}
}
})
const compSetup: ComponentInstance<typeof CompSetup> = {} as any

expectType<string | undefined>(compSetup.test)
expectType<number>(compSetup.a)
expectType<ComponentPublicInstance>(compSetup)
})
describe('functional component', () => {
// Functional
const CompFunctional: FunctionalComponent<{ test?: string }> = {} as any
const compFunctional: ComponentInstance<typeof CompFunctional> = {} as any

expectType<string | undefined>(compFunctional.test)
expectType<ComponentPublicInstance>(compFunctional)

const CompFunction: (props: { test?: string }) => any = {} as any
const compFunction: ComponentInstance<typeof CompFunction> = {} as any

expectType<string | undefined>(compFunction.test)
expectType<ComponentPublicInstance>(compFunction)
})

describe('options component', () => {
// Options
const CompOptions = defineComponent({
props: {
test: String
},
data() {
return {
a: 1
}
},
computed: {
b() {
return 'test'
}
},
methods: {
func(a: string) {
return true
}
}
})
const compOptions: ComponentInstance<typeof CompOptions> = {} as any
expectType<string | undefined>(compOptions.test)
expectType<number>(compOptions.a)
expectType<(a: string) => boolean>(compOptions.func)
expectType<ComponentPublicInstance>(compOptions)
})

describe('object no defineComponent', () => {
// object - no defineComponent

const CompObjectSetup = {
props: {
test: String
},
setup() {
return {
a: 1
}
}
}
const compObjectSetup: ComponentInstance<typeof CompObjectSetup> = {} as any
expectType<string | undefined>(compObjectSetup.test)
expectType<number>(compObjectSetup.a)
expectType<ComponentPublicInstance>(compObjectSetup)

const CompObjectData = {
props: {
test: String
},
data() {
return {
a: 1
}
}
}
const compObjectData: ComponentInstance<typeof CompObjectData> = {} as any
expectType<string | undefined>(compObjectData.test)
expectType<number>(compObjectData.a)
expectType<ComponentPublicInstance>(compObjectData)

const CompObjectNoProps = {
data() {
return {
a: 1
}
}
}
const compObjectNoProps: ComponentInstance<typeof CompObjectNoProps> =
{} as any
expectType<string | undefined>(compObjectNoProps.test)
expectType<number>(compObjectNoProps.a)
expectType<ComponentPublicInstance>(compObjectNoProps)
})

describe('Generic component', () => {
const Comp = defineComponent(
// TODO: babel plugin to auto infer runtime props options from type
// similar to defineProps<{...}>()
<T extends string | number>(props: { msg: T; list: T[] }) => {
// use Composition API here like in <script setup>
const count = ref(0)

return () => (
// return a render function (both JSX and h() works)
<div>
{props.msg} {count.value}
</div>
)
}
)

// defaults to known types since types are resolved on instantiation
const comp: ComponentInstance<typeof Comp> = {} as any
expectType<string | number>(comp.msg)
expectType<Array<string | number>>(comp.list)
})
33 changes: 33 additions & 0 deletions packages/runtime-core/src/component.ts
Expand Up @@ -83,6 +83,39 @@ import { LifecycleHooks } from './enums'

export type Data = Record<string, unknown>

/**
* Public utility type for extracting the instance type of a component.
* Works with all valid component definition types. This is intended to replace
* the usage of `InstanceType<typeof Comp>` which only works for
* constructor-based component definition types.
*
* Exmaple:
* ```ts
* const MyComp = { ... }
* declare const instance: ComponentInstance<typeof MyComp>
* ```
*/
export type ComponentInstance<T> = T extends { new (): ComponentPublicInstance }
? InstanceType<T>
: T extends FunctionalComponent<infer Props, infer Emits>
? ComponentPublicInstance<Props, {}, {}, {}, {}, Emits>
: T extends Component<
infer Props,
infer RawBindings,
infer D,
infer C,
infer M
>
? // NOTE we override Props/RawBindings/D to make sure is not `unknown`
ComponentPublicInstance<
unknown extends Props ? {} : Props,
unknown extends RawBindings ? {} : RawBindings,
unknown extends D ? {} : D,
C,
M
>
: never // not a vue Component

/**
* For extending allowed non-declared props on components in TSX
*/
Expand Down
3 changes: 2 additions & 1 deletion packages/runtime-core/src/index.ts
Expand Up @@ -230,7 +230,8 @@ export type {
ComponentInternalInstance,
SetupContext,
ComponentCustomProps,
AllowedComponentProps
AllowedComponentProps,
ComponentInstance
} from './component'
export type { DefineComponent, PublicProps } from './apiDefineComponent'
export type {
Expand Down

0 comments on commit bfb8565

Please sign in to comment.