-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
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
Further improve Vue type declarations for canonical usage #6391
Changes from all commits
5b5a88f
385a744
8cd5b9c
540a38f
f34f4f6
b1f40ce
355ff75
bc54007
e7ea5bb
ebde0b1
d78d14b
fc83771
a50c838
3c86b10
33a106c
0f586db
1092efe
ebd8c0b
c628103
e4a8545
3cac5c7
d55bc63
07eb21c
e7777d7
21d62a1
0958bda
6c76c45
5335788
8a1aab2
042e1c3
76de5dc
070460b
718dd7d
89f958b
da53a0a
e034641
c9f0939
754d0df
b784785
d5fbd81
37c5883
9e6931a
d9accb0
b90386a
67eab9c
97a401e
8df15b7
1ac0b7a
4649739
c953990
8901752
a7f84d9
5fa1ab9
a595239
00aa9d3
6091248
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,36 @@ | ||
import * as V from "./vue"; | ||
import * as Options from "./options"; | ||
import * as Plugin from "./plugin"; | ||
import * as VNode from "./vnode"; | ||
import { Vue } from "./vue"; | ||
|
||
// `Vue` in `export = Vue` must be a namespace | ||
// All available types are exported via this namespace | ||
declare namespace Vue { | ||
export type CreateElement = V.CreateElement; | ||
export default Vue; | ||
|
||
export type Component = Options.Component; | ||
export type AsyncComponent = Options.AsyncComponent; | ||
export type ComponentOptions<V extends Vue> = Options.ComponentOptions<V>; | ||
export type FunctionalComponentOptions = Options.FunctionalComponentOptions; | ||
export type RenderContext = Options.RenderContext; | ||
export type PropOptions = Options.PropOptions; | ||
export type ComputedOptions<V extends Vue> = Options.ComputedOptions<V>; | ||
export type WatchHandler<V extends Vue> = Options.WatchHandler<V, any>; | ||
export type WatchOptions = Options.WatchOptions; | ||
export type DirectiveFunction = Options.DirectiveFunction; | ||
export type DirectiveOptions = Options.DirectiveOptions; | ||
export { | ||
CreateElement | ||
} from "./vue"; | ||
|
||
export type PluginFunction<T> = Plugin.PluginFunction<T>; | ||
export type PluginObject<T> = Plugin.PluginObject<T>; | ||
export { | ||
Component, | ||
AsyncComponent, | ||
ComponentOptions, | ||
FunctionalComponentOptions, | ||
RenderContext, | ||
PropOptions, | ||
ComputedOptions, | ||
WatchHandler, | ||
WatchOptions, | ||
WatchOptionsWithHandler, | ||
DirectiveFunction, | ||
DirectiveOptions | ||
} from "./options"; | ||
|
||
export type VNodeChildren = VNode.VNodeChildren; | ||
export type VNodeChildrenArrayContents = VNode.VNodeChildrenArrayContents; | ||
export type VNode = VNode.VNode; | ||
export type VNodeComponentOptions = VNode.VNodeComponentOptions; | ||
export type VNodeData = VNode.VNodeData; | ||
export type VNodeDirective = VNode.VNodeDirective; | ||
} | ||
export { | ||
PluginFunction, | ||
PluginObject | ||
} from "./plugin"; | ||
|
||
// TS cannot merge imported class with namespace, declare a subclass to bypass | ||
declare class Vue extends V.Vue {} | ||
|
||
export = Vue; | ||
export { | ||
VNodeChildren, | ||
VNodeChildrenArrayContents, | ||
VNode, | ||
VNodeComponentOptions, | ||
VNodeData, | ||
VNodeDirective | ||
} from "./vnode"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,48 +1,88 @@ | ||
import { Vue, CreateElement } from "./vue"; | ||
import { Vue, CreateElement, CombinedVueInstance } from "./vue"; | ||
import { VNode, VNodeData, VNodeDirective } from "./vnode"; | ||
|
||
type Constructor = { | ||
new (...args: any[]): any; | ||
} | ||
|
||
export type Component = typeof Vue | ComponentOptions<Vue> | FunctionalComponentOptions; | ||
// we don't support infer props in async component | ||
export type Component<Data=DefaultData<Vue>, Methods=DefaultMethods<Vue>, Computed=DefaultComputed, Props=DefaultProps> = | ||
| typeof Vue | ||
| FunctionalComponentOptions<Props> | ||
| ThisTypedComponentOptionsWithArrayProps<Vue, Data, Methods, Computed, keyof Props> | ||
| ThisTypedComponentOptionsWithRecordProps<Vue, Data, Methods, Computed, Props>; | ||
|
||
interface EsModuleComponent { | ||
default: Component | ||
} | ||
|
||
export type AsyncComponent = ( | ||
resolve: (component: Component) => void, | ||
export type AsyncComponent<Data=DefaultData<Vue>, Methods=DefaultMethods<Vue>, Computed=DefaultComputed, Props=DefaultProps> = ( | ||
resolve: (component: Component<Data, Methods, Computed, Props>) => void, | ||
reject: (reason?: any) => void | ||
) => Promise<Component | EsModuleComponent> | Component | void; | ||
) => Promise<Component | EsModuleComponent> | void; | ||
|
||
/** | ||
* When the `Computed` type parameter on `ComponentOptions` is inferred, | ||
* it should have a property with the return type of every get-accessor. | ||
* Since there isn't a way to query for the return type of a function, we allow TypeScript | ||
* to infer from the shape of `Accessors<Computed>` and work backwards. | ||
*/ | ||
export type Accessors<T> = { | ||
[K in keyof T]: (() => T[K]) | ComputedOptions<T[K]> | ||
} | ||
|
||
export interface ComponentOptions<V extends Vue> { | ||
data?: Object | ((this: V) => Object); | ||
props?: string[] | { [key: string]: PropOptions | Constructor | Constructor[] }; | ||
/** | ||
* This type should be used when an array of strings is used for a component's `props` value. | ||
*/ | ||
export type ThisTypedComponentOptionsWithArrayProps<V extends Vue, Data, Methods, Computed, PropNames extends string> = | ||
object & | ||
ComponentOptions<V, Data | ((this: Readonly<Record<PropNames, any>> & V) => Data), Methods, Computed, PropNames[]> & | ||
ThisType<CombinedVueInstance<V, Data, Methods, Computed, Readonly<Record<PropNames, any>>>>; | ||
|
||
/** | ||
* This type should be used when an object mapped to `PropOptions` is used for a component's `props` value. | ||
*/ | ||
export type ThisTypedComponentOptionsWithRecordProps<V extends Vue, Data, Methods, Computed, Props> = | ||
object & | ||
ComponentOptions<V, Data | ((this: Readonly<Props> & V) => Data), Methods, Computed, RecordPropsDefinition<Props>> & | ||
ThisType<CombinedVueInstance<V, Data, Methods, Computed, Readonly<Props>>>; | ||
|
||
type DefaultData<V> = object | ((this: V) => object); | ||
type DefaultProps = Record<string, any>; | ||
type DefaultMethods<V> = { [key: string]: (this: V, ...args: any[]) => any }; | ||
type DefaultComputed = { [key: string]: any }; | ||
export interface ComponentOptions< | ||
V extends Vue, | ||
Data=DefaultData<V>, | ||
Methods=DefaultMethods<V>, | ||
Computed=DefaultComputed, | ||
PropsDef=PropsDefinition<DefaultProps>> { | ||
data?: Data; | ||
props?: PropsDef; | ||
propsData?: Object; | ||
computed?: { [key: string]: ((this: V) => any) | ComputedOptions<V> }; | ||
methods?: { [key: string]: (this: V, ...args: any[]) => any }; | ||
watch?: { [key: string]: ({ handler: WatchHandler<V, any> } & WatchOptions) | WatchHandler<V, any> | string }; | ||
computed?: Accessors<Computed>; | ||
methods?: Methods; | ||
watch?: Record<string, WatchOptionsWithHandler<any> | WatchHandler<any> | string>; | ||
|
||
el?: Element | String; | ||
template?: string; | ||
render?(this: V, createElement: CreateElement): VNode; | ||
render?(createElement: CreateElement): VNode; | ||
renderError?: (h: () => VNode, err: Error) => VNode; | ||
staticRenderFns?: ((createElement: CreateElement) => VNode)[]; | ||
|
||
beforeCreate?(this: V): void; | ||
created?(this: V): void; | ||
beforeDestroy?(this: V): void; | ||
destroyed?(this: V): void; | ||
beforeMount?(this: V): void; | ||
mounted?(this: V): void; | ||
beforeUpdate?(this: V): void; | ||
updated?(this: V): void; | ||
activated?(this: V): void; | ||
deactivated?(this: V): void; | ||
|
||
directives?: { [key: string]: DirectiveOptions | DirectiveFunction }; | ||
components?: { [key: string]: Component | AsyncComponent }; | ||
created?(): void; | ||
beforeDestroy?(): void; | ||
destroyed?(): void; | ||
beforeMount?(): void; | ||
mounted?(): void; | ||
beforeUpdate?(): void; | ||
updated?(): void; | ||
activated?(): void; | ||
deactivated?(): void; | ||
|
||
directives?: { [key: string]: DirectiveFunction | DirectiveOptions }; | ||
components?: { [key: string]: Component<any, any, any, any> | AsyncComponent<any, any, any, any> }; | ||
transitions?: { [key: string]: Object }; | ||
filters?: { [key: string]: Function }; | ||
|
||
|
@@ -57,49 +97,64 @@ export interface ComponentOptions<V extends Vue> { | |
parent?: Vue; | ||
mixins?: (ComponentOptions<Vue> | typeof Vue)[]; | ||
name?: string; | ||
// TODO: support properly inferred 'extends' | ||
extends?: ComponentOptions<Vue> | typeof Vue; | ||
delimiters?: [string, string]; | ||
comments?: boolean; | ||
inheritAttrs?: boolean; | ||
} | ||
|
||
export interface FunctionalComponentOptions { | ||
export interface FunctionalComponentOptions<Props = DefaultProps, PropDefs = PropsDefinition<Props>> { | ||
name?: string; | ||
props?: string[] | { [key: string]: PropOptions | Constructor | Constructor[] }; | ||
props?: PropDefs; | ||
inject?: { [key: string]: string | symbol } | string[]; | ||
functional: boolean; | ||
render(this: never, createElement: CreateElement, context: RenderContext): VNode | void; | ||
render(this: undefined, createElement: CreateElement, context: RenderContext<Props>): VNode; | ||
} | ||
|
||
export interface RenderContext { | ||
props: any; | ||
export interface RenderContext<Props=DefaultProps> { | ||
props: Props; | ||
children: VNode[]; | ||
slots(): any; | ||
data: VNodeData; | ||
parent: Vue; | ||
injections: any | ||
} | ||
|
||
export interface PropOptions { | ||
type?: Constructor | Constructor[] | null; | ||
export type Prop<T> = { (): T } | { new (...args: any[]): T & object } | ||
|
||
export type PropValidator<T> = PropOptions<T> | Prop<T> | Prop<T>[]; | ||
|
||
export interface PropOptions<T=any> { | ||
type?: Prop<T> | Prop<T>[]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is clever declaration :D |
||
required?: boolean; | ||
default?: any; | ||
validator?(value: any): boolean; | ||
default?: T | null | undefined | (() => object); | ||
validator?(value: T): boolean; | ||
} | ||
|
||
export interface ComputedOptions<V> { | ||
get?(this: V): any; | ||
set?(this: V, value: any): void; | ||
export type RecordPropsDefinition<T> = { | ||
[K in keyof T]: PropValidator<T[K]> | ||
} | ||
export type ArrayPropsDefinition<T> = (keyof T)[]; | ||
export type PropsDefinition<T> = ArrayPropsDefinition<T> | RecordPropsDefinition<T>; | ||
|
||
export interface ComputedOptions<T> { | ||
get?(): T; | ||
set?(value: T): void; | ||
cache?: boolean; | ||
} | ||
|
||
export type WatchHandler<V, T> = (this: V, val: T, oldVal: T) => void; | ||
export type WatchHandler<T> = (val: T, oldVal: T) => void; | ||
|
||
export interface WatchOptions { | ||
deep?: boolean; | ||
immediate?: boolean; | ||
} | ||
|
||
export interface WatchOptionsWithHandler<T> extends WatchOptions { | ||
handler: WatchHandler<T>; | ||
} | ||
|
||
export type DirectiveFunction = ( | ||
el: HTMLElement, | ||
binding: VNodeDirective, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import Vue = require("../index"); | ||
import Vue from "../index"; | ||
|
||
declare module "../vue" { | ||
// add instance property and method | ||
|
@@ -8,9 +8,9 @@ declare module "../vue" { | |
} | ||
|
||
// add static property and method | ||
namespace Vue { | ||
const staticProperty: string; | ||
function staticMethod(): void; | ||
interface VueConstructor { | ||
staticProperty: string; | ||
staticMethod(): void; | ||
} | ||
} | ||
|
||
|
@@ -22,10 +22,21 @@ declare module "../options" { | |
} | ||
|
||
const vm = new Vue({ | ||
props: ["bar"], | ||
data: { | ||
a: true | ||
}, | ||
foo: "foo" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should keep this line to test There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops, unintentional deletion during refactoring. Thanks for pointing out! |
||
foo: "foo", | ||
methods: { | ||
foo() { | ||
this.a = false; | ||
} | ||
}, | ||
computed: { | ||
BAR(): string { | ||
return this.bar.toUpperCase(); | ||
} | ||
} | ||
}); | ||
|
||
vm.$instanceProperty; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like ThisType of
computed
,watch
,render
and lifecycle hooks will be invalid if the users annotate their component options withas ComponentOptions<Something>
.Can we deal with in that case too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type assertion like
{ opt: ...} as Option
would not bring type inference to the declaration.Users have to annotate Vue.extend or casting
this
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. Then I think we should stated about this in docs for migration 🙂