Skip to content

Commit 1c64108

Browse files
authoredJun 24, 2020
fix(type): improve defineComponent type for option apis (#406)
* fix(type): improve defineComponent type for option apis * test: add dts tests for defineComponent fix
1 parent 048b6d3 commit 1c64108

9 files changed

+854
-171
lines changed
 

‎src/component/common.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type Data = { [key: string]: unknown }

‎src/component/component.ts

-161
This file was deleted.

‎src/component/componentOptions.ts

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { Data } from './common'
2+
import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
3+
import { VNode } from 'vue'
4+
import { ComponentInstance, ComponentRenderProxy } from './componentProxy'
5+
6+
import { ComponentOptions as Vue2ComponentOptions } from 'vue'
7+
8+
export interface SetupContext {
9+
readonly attrs: Record<string, string>
10+
readonly slots: { [key: string]: (...args: any[]) => VNode[] }
11+
readonly parent: ComponentInstance | null
12+
readonly root: ComponentInstance
13+
readonly listeners: { [key: string]: Function }
14+
15+
emit(event: string, ...args: any[]): void
16+
}
17+
18+
export type ComputedGetter<T> = (ctx?: any) => T
19+
export type ComputedSetter<T> = (v: T) => void
20+
21+
export interface WritableComputedOptions<T> {
22+
get: ComputedGetter<T>
23+
set: ComputedSetter<T>
24+
}
25+
26+
export type ComputedOptions = Record<
27+
string,
28+
ComputedGetter<any> | WritableComputedOptions<any>
29+
>
30+
31+
export interface MethodOptions {
32+
[key: string]: Function
33+
}
34+
35+
export type SetupFunction<Props, RawBindings> = (
36+
this: void,
37+
props: Props,
38+
ctx: SetupContext
39+
) => RawBindings | (() => VNode | null)
40+
41+
interface ComponentOptionsBase<
42+
Props,
43+
D = Data,
44+
C extends ComputedOptions = {},
45+
M extends MethodOptions = {}
46+
>
47+
extends Omit<
48+
Vue2ComponentOptions<Vue, D, M, C, Props>,
49+
'data' | 'computed' | 'method' | 'setup' | 'props'
50+
> {
51+
data?: (this: Props, vm: Props) => D
52+
computed?: C
53+
methods?: M
54+
}
55+
56+
export type ExtractComputedReturns<T extends any> = {
57+
[key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }
58+
? TReturn
59+
: T[key] extends (...args: any[]) => infer TReturn
60+
? TReturn
61+
: never
62+
}
63+
64+
export type ComponentOptionsWithProps<
65+
PropsOptions = ComponentPropsOptions,
66+
RawBindings = Data,
67+
D = Data,
68+
C extends ComputedOptions = {},
69+
M extends MethodOptions = {},
70+
Props = ExtractPropTypes<PropsOptions>
71+
> = ComponentOptionsBase<Props, D, C, M> & {
72+
props?: PropsOptions
73+
setup?: SetupFunction<Props, RawBindings>
74+
} & ThisType<ComponentRenderProxy<Props, RawBindings, D, C, M>>
75+
76+
export type ComponentOptionsWithArrayProps<
77+
PropNames extends string = string,
78+
RawBindings = Data,
79+
D = Data,
80+
C extends ComputedOptions = {},
81+
M extends MethodOptions = {},
82+
Props = Readonly<{ [key in PropNames]?: any }>
83+
> = ComponentOptionsBase<Props, D, C, M> & {
84+
props?: PropNames[]
85+
setup?: SetupFunction<Props, RawBindings>
86+
} & ThisType<ComponentRenderProxy<Props, RawBindings, D, C, M>>
87+
88+
export type ComponentOptionsWithoutProps<
89+
Props = unknown,
90+
RawBindings = Data,
91+
D = Data,
92+
C extends ComputedOptions = {},
93+
M extends MethodOptions = {}
94+
> = ComponentOptionsBase<Props, D, C, M> & {
95+
props?: undefined
96+
setup?: SetupFunction<Props, RawBindings>
97+
} & ThisType<ComponentRenderProxy<Props, RawBindings, D, C, M>>
98+
99+
export type WithLegacyAPI<T, D, C, M, Props> = T &
100+
Omit<Vue2ComponentOptions<Vue, D, M, C, Props>, keyof T>

‎src/component/componentProps.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Data } from './component'
1+
import { Data } from './common'
22

33
export type ComponentPropsOptions<P = Data> =
44
| ComponentObjectPropsOptions<P>

‎src/component/componentProxy.ts

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { ExtractPropTypes } from './componentProps'
2+
import { UnwrapRef } from '..'
3+
import { Data } from './common'
4+
5+
import Vue, {
6+
VueConstructor,
7+
ComponentOptions as Vue2ComponentOptions,
8+
} from 'vue'
9+
import {
10+
ComputedOptions,
11+
MethodOptions,
12+
ExtractComputedReturns,
13+
} from './componentOptions'
14+
15+
export type ComponentInstance = InstanceType<VueConstructor>
16+
17+
// public properties exposed on the proxy, which is used as the render context
18+
// in templates (as `this` in the render option)
19+
export type ComponentRenderProxy<
20+
P = {}, // props type extracted from props option
21+
B = {}, // raw bindings returned from setup()
22+
D = {}, // return from data()
23+
C extends ComputedOptions = {},
24+
M extends MethodOptions = {},
25+
PublicProps = P
26+
> = {
27+
$data: D
28+
$props: Readonly<P & PublicProps>
29+
$attrs: Data
30+
$refs: Data
31+
$slots: Data
32+
$root: ComponentInstance | null
33+
$parent: ComponentInstance | null
34+
$emit: (event: string, ...args: unknown[]) => void
35+
} & Readonly<P> &
36+
UnwrapRef<B> &
37+
D &
38+
M &
39+
ExtractComputedReturns<C> &
40+
Vue
41+
42+
// for Vetur and TSX support
43+
type VueConstructorProxy<PropsOptions, RawBindings> = VueConstructor & {
44+
new (...args: any[]): ComponentRenderProxy<
45+
ExtractPropTypes<PropsOptions>,
46+
UnwrapRef<RawBindings>,
47+
ExtractPropTypes<PropsOptions, false>
48+
>
49+
}
50+
51+
type DefaultData<V> = object | ((this: V) => object)
52+
type DefaultMethods<V> = { [key: string]: (this: V, ...args: any[]) => any }
53+
type DefaultComputed = { [key: string]: any }
54+
55+
export type VueProxy<
56+
PropsOptions,
57+
RawBindings,
58+
Data = DefaultData<Vue>,
59+
Computed = DefaultComputed,
60+
Methods = DefaultMethods<Vue>
61+
> = Vue2ComponentOptions<
62+
Vue,
63+
UnwrapRef<RawBindings> & Data,
64+
Methods,
65+
Computed,
66+
PropsOptions,
67+
ExtractPropTypes<PropsOptions, false>
68+
> &
69+
VueConstructorProxy<PropsOptions, RawBindings>

‎src/component/defineComponent.ts

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import Vue from 'vue'
2+
import { ComponentPropsOptions } from './componentProps'
3+
import {
4+
MethodOptions,
5+
ComputedOptions,
6+
ComponentOptionsWithoutProps,
7+
ComponentOptionsWithArrayProps,
8+
ComponentOptionsWithProps,
9+
} from './componentOptions'
10+
import { VueProxy } from './componentProxy'
11+
import { Data } from './common'
12+
import { HasDefined } from '../types/basic'
13+
14+
// overload 1: object format with no props
15+
export function defineComponent<
16+
RawBindings,
17+
D = Data,
18+
C extends ComputedOptions = {},
19+
M extends MethodOptions = {}
20+
>(
21+
options: ComponentOptionsWithoutProps<unknown, RawBindings, D, C, M>
22+
): VueProxy<unknown, RawBindings, D, C, M>
23+
24+
// overload 2: object format with array props declaration
25+
// props inferred as { [key in PropNames]?: any }
26+
// return type is for Vetur and TSX support
27+
export function defineComponent<
28+
PropNames extends string,
29+
RawBindings = Data,
30+
D = Data,
31+
C extends ComputedOptions = {},
32+
M extends MethodOptions = {},
33+
PropsOptions extends ComponentPropsOptions = ComponentPropsOptions
34+
>(
35+
options: ComponentOptionsWithArrayProps<PropNames, RawBindings, D, C, M>
36+
): VueProxy<Readonly<{ [key in PropNames]?: any }>, RawBindings, D, C, M>
37+
38+
// overload 3: object format with object props declaration
39+
// see `ExtractPropTypes` in ./componentProps.ts
40+
export function defineComponent<
41+
Props,
42+
RawBindings = Data,
43+
D = Data,
44+
C extends ComputedOptions = {},
45+
M extends MethodOptions = {},
46+
PropsOptions extends ComponentPropsOptions = ComponentPropsOptions
47+
>(
48+
options: HasDefined<Props> extends true
49+
? ComponentOptionsWithProps<PropsOptions, RawBindings, D, C, M, Props>
50+
: ComponentOptionsWithProps<PropsOptions, RawBindings, D, C, M>
51+
): VueProxy<PropsOptions, RawBindings, D, C, M>
52+
// implementation, close to no-op
53+
export function defineComponent(options: any) {
54+
return options as any
55+
}
56+
57+
export const createComponent = ((options: any) => {
58+
if (__DEV__) {
59+
Vue.util.warn('`createComponent` has been renamed to `defineComponent`.')
60+
}
61+
return defineComponent(options)
62+
}) as typeof defineComponent

‎src/component/index.ts

+4-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
1-
export {
2-
Data,
3-
createComponent,
4-
defineComponent,
5-
SetupFunction,
6-
SetupContext,
7-
ComponentInstance,
8-
ComponentRenderProxy,
9-
} from './component'
1+
export { defineComponent, createComponent } from './defineComponent'
2+
export { SetupFunction, SetupContext } from './componentOptions'
3+
export { ComponentInstance, ComponentRenderProxy } from './componentProxy'
4+
export { Data } from './common'
105
export { PropType, PropOptions } from './componentProps'

‎test-dts/defineComponent.test-d.ts

+615
Large diffs are not rendered by default.

‎test-dts/index.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
export * from '@vue/composition-api'
22

3+
export function describe(_name: string, _fn: () => void): void
4+
35
export function expectType<T>(value: T): void
46
export function expectError<T>(value: T): void
57
export function expectAssignable<T, T2 extends T = T>(value: T2): void

0 commit comments

Comments
 (0)
Please sign in to comment.