Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(types): ComponentInstance type #5408

Merged
merged 4 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions packages/dts-test/componentInstance.test-d.tsx
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,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
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ export type {
ComponentInternalInstance,
SetupContext,
ComponentCustomProps,
AllowedComponentProps
AllowedComponentProps,
ComponentInstance
} from './component'
export type { DefineComponent } from './apiDefineComponent'
export type {
Expand Down