前言
虽然大家说 Vue 2 对于 TypeScript 支持不够好,用起来比较麻烦,但是这里边 Vue 也依旧做了很多事情,去做类型支持的能力。同时也官方也给出了另一个解法 github.com/vuejs/vue-c… ,当然 vue-class-component 不仅仅对于类型这块有了更好的支持,它最核心的是用类的方式写 Vue 组件。
今天我们这里更多的不是讨论 vue-class-component,而是重点关注与 Vue 2 本身对于类型支持这块所做的事情,一起来学习下对于一个并不是用 TS 写的项目,是怎么做类型支持的,对于我们自身的场景而言,有哪些可以借鉴的地方。
正文分析
What
类型支持其实就是对 TypeScript 的支持,虽然是片面的理解,但是也大概可以这么理解。那对于 TypeScript 的支持到底是个啥呢,做啥的?
这个首先就要认识 TypeScript,官网在这里 www.typescriptlang.org/,最核心的官方介绍(翻译过后的大概含义):TypeScript 就是带有类型语法的 JavaScript。TypeScript 给 JavaScript 提供了静态类型系统,可以真实有效地帮助我们防止很多运行时错误;此外,借助于 IDE 的支持,搭配静态类型系统,可以给到开发者很好的智能提示,“堆起”代码来速度快了不少。
那么 Vue 对于 TypeScript 的支持,核心就是要让 TypeScript 正确推断出 Vue 本身以及开发者定义的组件的类型,进而开发者使用的时候就可以获得对应的类型推断。详细的一些介绍文章可以参考文档 cn.vuejs.org/v2/guide/ty…
有了对 TypeScript 的支持之后,我们去写组件的时候,一个大概的效果应该是这样的:
可以看到,可以正确提醒 this
上的属性,如 type 这个 Prop,同时也会有原型上的 $nextTick
这样的方法的提醒。
How
那么这个支持是如何做的,一起看下 Vue 的处理。
增加类型入口声明
源文件在 github.com/vuejs/vue/b…。按照 TypeScript 规范,在 package.json 中新增typings
的声明,指向类型声明文件。
类型声明文件
声明文件,github.com/vuejs/vue/b…,这里看后缀 .d.ts
,这个在 TypeScript 中也是一个 Feature——类型声明文件,有详细的介绍 www.typescriptlang.org/docs/handbo…。
借助于类型声明文件,即使没有相关 TS 声明的项目,我们也可以给这样的项目专门增加类型相关的声明,这样就达到了对于 TypeScript 类型支持的目的。最经典的项目就是 github.com/DefinitelyT…。
那先来看下一下 index.d.ts 中的大概内容:
import { Vue } from "./vue";
import "./umd";
// 把 Vue 暴露出去
export default Vue;
export {
CreateElement,
VueConstructor
} from "./vue";
export {
Component,
AsyncComponent,
ComponentOptions,
FunctionalComponentOptions,
RenderContext,
PropType,
PropOptions,
ComputedOptions,
WatchHandler,
WatchOptions,
WatchOptionsWithHandler,
DirectiveFunction,
DirectiveOptions
} from "./options";
export {
PluginFunction,
PluginObject
} from "./plugin";
export {
VNodeChildren,
VNodeChildrenArrayContents,
VNode,
VNodeComponentOptions,
VNodeData,
VNodeDirective
} from "./vnode";
一样做了模块的拆分,把各个模块暴露出来的 export 出去。这里我们重点分析下两个模块:./vue
模块和./options
模块。
先看下最最最核心的 github.com/vuejs/vue/b…
import {
Component,
AsyncComponent,
ComponentOptions,
FunctionalComponentOptions,
WatchOptionsWithHandler,
WatchHandler,
DirectiveOptions,
DirectiveFunction,
RecordPropsDefinition,
ThisTypedComponentOptionsWithArrayProps,
ThisTypedComponentOptionsWithRecordProps,
WatchOptions,
} from "./options";
import { VNode, VNodeData, VNodeChildren, NormalizedScopedSlot } from "./vnode";
import { PluginFunction, PluginObject } from "./plugin";
export interface CreateElement {
// $createElement 的类型,这里函数接口的重载
// 区分了带有 data 的情况 还是没带 data 的情况
(tag?: string | Component<any, any, any, any> | AsyncComponent<any, any, any, any> | (() => Component), children?: VNodeChildren): VNode;
(tag?: string | Component<any, any, any, any> | AsyncComponent<any, any, any, any> | (() => Component), data?: VNodeData, children?: VNodeChildren): VNode;
}
// Vue 实例类型
export interface Vue {
// 各种 API 的定义
readonly $el: Element;
readonly $options: ComponentOptions<Vue>;
readonly $parent: Vue;
readonly $root: Vue;
readonly $children: Vue[];
readonly $refs: { [key: string]: Vue | Element | (Vue | Element)[] | undefined };
readonly $slots: { [key: string]: VNode[] | undefined };
readonly $scopedSlots: { [key: string]: NormalizedScopedSlot | undefined };
readonly $isServer: boolean;
readonly $data: Record<string, any>;
readonly $props: Record<string, any>;
readonly $ssrContext: any;
readonly $vnode: VNode;
readonly $attrs: Record<string, string>;
readonly $listeners: Record<string, Function | Function[]>;
// 支持链式调用 返回值 this
$mount(elementOrSelector?: Element | string, hydrating?: boolean): this;
$forceUpdate(): void;
$destroy(): void;
// 利用 typeof 直接获得 Vue.set 的类型
$set: typeof Vue.set;
$delete: typeof Vue.delete;
// $watch 函数重载
$watch(
expOrFn: string,
callback: (this: this, n: any, o: any) => void,
options?: WatchOptions
): (() => void);
// 类型自动匹配,获取泛型 T
$watch<T>(
expOrFn: (this: this) => T,
callback: (this: this, n: T, o: T) => void,
options?: WatchOptions
): (() => void);
$on(event: string | string[], callback: Function): this;
$once(event: string | string[], callback: Function): this;
$off(event?: string | string[], callback?: Function): this;
$emit(event: string, ...args: any[]): this;
$nextTick(callback: (this: this) => void): void;
$nextTick(): Promise<void>;
$createElement: CreateElement;
}
// 合并后的 Vue 实例,主要考虑传入的 options 的情况,需要把 data methods 等等的值 merge 到 this 上下文中
export type CombinedVueInstance<Instance extends Vue, Data, Methods, Computed, Props> = Data & Methods & Computed & Props & Instance;
// Vue.extend 模式所最终得到的 Vue 构造器
export type ExtendedVue<Instance extends Vue, Data, Methods, Computed, Props> = VueConstructor<CombinedVueInstance<Instance, Data, Methods, Computed, Props> & Vue>;
// Vue.config 配置项 中可以配置的内容
export interface VueConfiguration {
silent: boolean;
optionMergeStrategies: any;
devtools: boolean;
productionTip: boolean;
performance: boolean;
errorHandler(err: Error, vm: Vue, info: string): void;
warnHandler(msg: string, vm: Vue, trace: string): void;
ignoredElements: (string | RegExp)[];
keyCodes: { [key: string]: number | number[] };
async: boolean;
}
// Vue 构造器
export interface VueConstructor<V extends Vue = Vue> {
// new 构造器的重载,这个是TS对于用 new 这种方式创建实例 所提供的类型声明方式
// https://www.typescriptlang.org/docs/handbook/2/functions.html#construct-signatures
new <Data = object, Methods = object, Computed = object, PropNames extends string = never>(options?: ThisTypedComponentOptionsWithArrayProps<V, Data, Methods, Computed, PropNames>): CombinedVueInstance<V, Data, Methods, Computed, Record<PropNames, any>>;
// ideally, the return type should just contain Props, not Record<keyof Props, any>. But TS requires to have Base constructors with the same return type.
new <Data = object, Methods = object, Computed = object, Props = object>(options?: ThisTypedComponentOptionsWithRecordProps<V, Data, Methods, Computed, Props>): CombinedVueInstance<V, Data, Methods, Computed, Record<keyof Props, any>>;
new (options?: ComponentOptions<V>): CombinedVueInstance<V, object, object, object, Record<keyof object, any>>;
extend<Data, Methods, Computed, PropNames extends string = never>(options?: ThisTypedComponentOptionsWithArrayProps<V, Data, Methods, Computed, PropNames>): ExtendedVue<V, Data, Methods, Computed, Record<PropNames, any>>;
extend<Data, Methods, Computed, Props>(options?: ThisTypedComponentOptionsWithRecordProps<V, Data, Methods, Computed, Props>): ExtendedVue<V, Data, Methods, Computed, Props>;
extend<PropNames extends string = never>(definition: FunctionalComponentOptions<Record<PropNames, any>, PropNames[]>): ExtendedVue<V, {}, {}, {}, Record<PropNames, any>>;
extend<Props>(definition: FunctionalComponentOptions<Props, RecordPropsDefinition<Props>>): ExtendedVue<V, {}, {}, {}, Props>;
extend(options?: ComponentOptions<V>): ExtendedVue<V, {}, {}, {}, {}>;
nextTick<T>(callback: (this: T) => void, context?: T): void;
nextTick(): Promise<void>
set<T>(object: object, key: string | number, value: T): T;
set<T>(array: T[], key: number, value: T): T;
delete(object: object, key: string | number): void;
delete<T>(array: T[], key: number): void;
directive(
id: string,
definition?: DirectiveOptions | DirectiveFunction
): DirectiveOptions;
filter(id: string, definition?: Function): Function;
component(id: string): VueConstructor;
component<VC extends VueConstructor>(id: string, constructor: VC): VC;
component<Data, Methods, Computed, Props>(id: string, definition: AsyncComponent<Data, Methods, Computed, Props>): ExtendedVue<V, Data, Methods, Computed, Props>;
component<Data, Methods, Computed, PropNames extends string = never>(id: string, definition?: ThisTypedComponentOptionsWithArrayProps<V, Data, Methods, Computed, PropNames>): ExtendedVue<V, Data, Methods, Computed, Record<PropNames, any>>;
component<Data, Methods, Computed, Props>(id: string, definition?: ThisTypedComponentOptionsWithRecordProps<V, Data, Methods, Computed, Props>): ExtendedVue<V, Data, Methods, Computed, Props>;
component<PropNames extends string>(id: string, definition: FunctionalComponentOptions<Record<PropNames, any>, PropNames[]>): ExtendedVue<V, {}, {}, {}, Record<PropNames, any>>;
component<Props>(id: string, definition: FunctionalComponentOptions<Props, RecordPropsDefinition<Props>>): ExtendedVue<V, {}, {}, {}, Props>;
component(id: string, definition?: ComponentOptions<V>): ExtendedVue<V, {}, {}, {}, {}>;
use<T>(plugin: PluginObject<T> | PluginFunction<T>, options?: T): VueConstructor<V>;
use(plugin: PluginObject<any> | PluginFunction<any>, ...options: any[]): VueConstructor<V>;
mixin(mixin: VueConstructor | ComponentOptions<Vue>): VueConstructor<V>;
compile(template: string): {
render(createElement: typeof Vue.prototype.$createElement): VNode;
staticRenderFns: (() => VNode)[];
};
observable<T>(obj: T): T;
util: {
warn(msg: string, vm?: InstanceType<VueConstructor>): void;
};
config: VueConfiguration;
version: string;
}
// 暴露出 Vue 值,这个值的类型就是 VueConstructor
export const Vue: VueConstructor;
我们先来看下这里边的一些知识点。
- 两个 Vue 的迷惑,我们需要分清楚,上边的
interface Vue
声明的是一个类型,实际的含义是 Vue 实例,最后暴露出去的const Vue
是声明暴露出去的一个值,这个值的类型是VueConstructor
也就是 Vue 构造器。TS会自动根据你使用的场景来决定使用哪个含义- 首先你需要知道,在TS中存在类型 Types、值 Values、命名空间 Namespaces,他们的含义:www.typescriptlang.org/docs/handbo…
- 在 TS 中是支持这种方式合并的,因为他们两个虽然同名,但是含义不同,相关文档 www.typescriptlang.org/docs/handbo…
- 而且你还可以通过TS的文档知道,这个行为和你声明一个 class 的时候一样,
class C
相对应的,在TS中会创建两个声明:一个叫C
的类型,对应的就是 C 的实例,另一个是 C 的值,这个值就是指向的构造器函数。class 算是一种内置处理 www.typescriptlang.org/docs/handbo…
this
作为类型使用,此时的 this 是动态的,他会动态指向到调用方所对应的类型,一下两个相关介绍可以供参考:- 构造符,类似
new ()
的方式声明当使用 new 创建实例的时候应该返回什么类型,依旧支持重载 - 类型的“递归”处理,例如
extend
、component
API 对于类型的处理 - 各种重载的使用,值得注意的是
CreateElement
的处理 - 基础的 Union Types www.typescriptlang.org/docs/handbo… 以及 Intersection Types www.typescriptlang.org/docs/handbo… 的使用
InstanceType
以及typeof
的使用
除了上述的,我们还可以发现一些重载的时候区分了不同的组件以及 Options,这些都是来自于./options
模块,我们再来看下里边的核心实现,来自文件 github.com/vuejs/vue/b…:
import { Vue, CreateElement, CombinedVueInstance } from "./vue";
import { VNode, VNodeData, VNodeDirective, NormalizedScopedSlot } from "./vnode";
type Constructor = {
new (...args: any[]): any;
}
// we don't support infer props in async component
// N.B. ComponentOptions<V> is contravariant, the default generic should be bottom type
// 关于 covariance 以及 contravariance https://www.stephanboyer.com/post/132/what-are-covariance-and-contravariance
// 中文的 https://jkchao.github.io/typescript-book-chinese/tips/covarianceAndContravariance.html
export type Component<Data=DefaultData<never>, Methods=DefaultMethods<never>, Computed=DefaultComputed, Props=DefaultProps> =
| typeof Vue
| FunctionalComponentOptions<Props>
| ComponentOptions<never, Data, Methods, Computed, Props>
type EsModule<T> = T | { default: T }
type ImportedComponent<Data=DefaultData<never>, Methods=DefaultMethods<never>, Computed=DefaultComputed, Props=DefaultProps>
= EsModule<Component<Data, Methods, Computed, Props>>
export type AsyncComponent<Data=DefaultData<never>, Methods=DefaultMethods<never>, Computed=DefaultComputed, Props=DefaultProps>
= AsyncComponentPromise<Data, Methods, Computed, Props>
| AsyncComponentFactory<Data, Methods, Computed, Props>
export type AsyncComponentPromise<Data=DefaultData<never>, Methods=DefaultMethods<never>, Computed=DefaultComputed, Props=DefaultProps> = (
resolve: (component: Component<Data, Methods, Computed, Props>) => void,
reject: (reason?: any) => void
) => Promise<ImportedComponent<Data, Methods, Computed, Props>> | void;
export type AsyncComponentFactory<Data=DefaultData<never>, Methods=DefaultMethods<never>, Computed=DefaultComputed, Props=DefaultProps> = () => {
component: Promise<ImportedComponent<Data, Methods, Computed, Props>>;
loading?: ImportedComponent;
error?: ImportedComponent;
delay?: number;
timeout?: number;
}
/**
* 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> = {
// 此处利用 https://www.typescriptlang.org/docs/handbook/2/mapped-types.html
[K in keyof T]: (() => T[K]) | ComputedOptions<T[K]>
}
type DataDef<Data, Props, V> = Data | ((this: Readonly<Props> & V) => Data)
// ThisTypedXxxx 加了 this type 增强的 this 上下文,关于 this type https://www.typescriptlang.org/docs/handbook/utility-types.html#thistypetype
/**
* 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, DataDef<Data, Record<PropNames, any>, V>, Methods, Computed, PropNames[], Record<PropNames, any>> &
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, DataDef<Data, Props, V>, Methods, Computed, RecordPropsDefinition<Props>, 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 };
// 基础的 ComponentOptions interface
export interface ComponentOptions<
V extends Vue,
Data=DefaultData<V>,
Methods=DefaultMethods<V>,
Computed=DefaultComputed,
PropsDef=PropsDefinition<DefaultProps>,
Props=DefaultProps> {
// 类型推断 自动推断出泛型 Data Methods 等
data?: Data;
props?: PropsDef;
propsData?: object;
computed?: Accessors<Computed>;
methods?: Methods;
watch?: Record<string, WatchOptionsWithHandler<any> | WatchHandler<any>>;
el?: Element | string;
template?: string;
// hack is for functional component type inference, should not be used in user code
render?(createElement: CreateElement, hack: RenderContext<Props>): VNode;
renderError?(createElement: CreateElement, err: Error): VNode;
staticRenderFns?: ((createElement: CreateElement) => VNode)[];
beforeCreate?(this: V): void;
created?(): void;
beforeDestroy?(): void;
destroyed?(): void;
beforeMount?(): void;
mounted?(): void;
beforeUpdate?(): void;
updated?(): void;
activated?(): void;
deactivated?(): void;
errorCaptured?(err: Error, vm: Vue, info: string): boolean | void;
serverPrefetch?(this: V): Promise<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 };
provide?: object | (() => object);
inject?: InjectOptions;
model?: {
prop?: string;
event?: string;
};
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<Props = DefaultProps, PropDefs = PropsDefinition<Props>> {
name?: string;
props?: PropDefs;
model?: {
prop?: string;
event?: string;
};
inject?: InjectOptions;
functional: boolean;
render?(this: undefined, createElement: CreateElement, context: RenderContext<Props>): VNode | VNode[];
}
export interface RenderContext<Props=DefaultProps> {
props: Props;
children: VNode[];
slots(): any;
data: VNodeData;
parent: Vue;
listeners: { [key: string]: Function | Function[] };
scopedSlots: { [key: string]: NormalizedScopedSlot };
injections: any
}
// 作用 String => string
export type Prop<T> = { (): T } | { new(...args: never[]): T & object } | { new(...args: string[]): Function }
export type PropType<T> = Prop<T> | Prop<T>[];
export type PropValidator<T> = PropOptions<T> | PropType<T>;
// PropOptions 接口
export interface PropOptions<T=any> {
type?: PropType<T>;
required?: boolean;
default?: T | null | undefined | (() => T | null | undefined);
validator?(value: T): boolean;
}
// 再次利用 keyof 得到 mapped type
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<T> = string | ((val: T, oldVal: T) => void);
export interface WatchOptions {
deep?: boolean;
immediate?: boolean;
}
export interface WatchOptionsWithHandler<T> extends WatchOptions {
handler: WatchHandler<T>;
}
export interface DirectiveBinding extends Readonly<VNodeDirective> {
readonly modifiers: { [key: string]: boolean };
}
export type DirectiveFunction = (
el: HTMLElement,
binding: DirectiveBinding,
vnode: VNode,
oldVnode: VNode
) => void;
export interface DirectiveOptions {
bind?: DirectiveFunction;
inserted?: DirectiveFunction;
update?: DirectiveFunction;
componentUpdated?: DirectiveFunction;
unbind?: DirectiveFunction;
}
export type InjectKey = string | symbol;
export type InjectOptions = {
[key: string]: InjectKey | { from?: InjectKey, default?: any }
} | string[];
可以看到这里边有一些独特的知识点会涉及:
- 关于 covariance 以及 contravariance ,有一些翻译是对应的 协变 和 逆变
- Mapped Types www.typescriptlang.org/docs/handbo…
- 关于 keyof www.typescriptlang.org/docs/handbo…
- 关于 this type www.typescriptlang.org/docs/handbo…
- 关于 never www.typescriptlang.org/docs/handbo…
如果我们将核心的两部分./vue
和 ./options
两部分连起来就可以分析出来,Vue 里边是怎么做类型增强的,尤其是和 this
相关处理, 我们就以 extend 举例:
extend
会存在重载,重载的核心依据就是options
参数的不同类型:ThisTypedComponentOptionsWithArrayProps、ThisTypedComponentOptionsWithRecordProps、FunctionalComponentOptions,ComponentOptions 作为降级的情况下使用- 这几个的核心区别都在 props 的不同,划分为了 array 的情况、Record 的情况(即 对象)、FunctionalComponentOptions 以及降级的 ComponentOptions 的情况
- 其中 FunctionalComponentOptions 又划分为了 array 和 对象的情况
- 这里的类型匹配的核心是利用TS的类型推导能力,这个也是和函数重载对齐配合使用的
- 类型推导完成之后,获得对应的泛型参数,如 Props、Data、Methods、Computed 等
- 基于以上推导出来的参数,利用 this type 增强配置项中写的时候
this
的值,相对应的也可以得出组件实例上的值
这样就实现了类型增强的能力,这里也只是看了一部分的类型增强实现,所有的case可以参考 vue 官方仓库中的 github.com/vuejs/vue/t… 模块,里边有针对于所有的类型增强相关的测试。
类型扩充
对于使用者而言,如果想要扩充 Vue 实例类型或者配置项的话,Vue 官方类型增强文档中也给出具体的做法 ,详情可参考 cn.vuejs.org/v2/guide/ty… 大概如下:
那这里又是什么原理呢?核心就是靠 www.typescriptlang.org/docs/handbo… 这个特性。
这个的基础就是TS所能提供的声明合并能力,详细可以参考 www.typescriptlang.org/docs/handbo… 文档。
简化的解释就是,在 TS 中 interface 的值是可以合并的,如果多个模块对于同一个 interface 进行声明的话,这个 interface 的值是会合并到一起的。
相关深入的一个补充介绍,可以参考 www.typescriptlang.org/docs/handbo…
Why
TS在整个社区的发展超出想象,绝大部分很多的框架、库基本都采用了TS开发,受限于历史原因,Vue 2 并没有采用 TS 实现(背景参考尤大自己的回答 www.zhihu.com/question/46…)。
同时随着 VS Code 的发展,对于 TS 的内置支持,或者其他 IDE 对于 TS 的支持,大家使用 TS 可以获得很好的体验。
那 Vue 的用户也是一样,大家对于 TS 的诉求会越来越多,对于 TS 类型这块的支持也就提上来了。
当然,我们也可以看到这里边做类型支持的成本还是挺高的,受限于 TS,受限于成本,想要支持的很完美基本不大可能。但是在 Vue 3 中包括利用 TS 实现源码,做了很多的特殊类型增强处理,尤其是也随着 Composition API 的标准化和普及,同时 VS Code 相关配套插件的支持,可以说在类型支持这块相比较于 Vue 2 强很多,有了一个质的提升,当然,你也可以选择使用 TSX,也可以获得很好的体验。
总结
整体 Vue 2 核心对于类型支持这块的实现,我们有了一个大概的了解和分析,涉及到的很多 TS 的特性也是非常之多的,大部分的知识点,我们在上述分析的过程中也都做了相应的一些总结,这些是需要大家花时间和精力研究一下,学习一下。
相信通过大概的了解,我们知道了作为一个提供者,怎么做 TypeScript 的类型声明,开发者应该如何使用,如何扩展。
我们团队之前也有一些相关的实践,如果有兴趣的同学可以参考:
- BetterScroll 2.0 的类型推导文章中也有很多特性的应用的实战介绍,可以参考 juejin.cn/post/689664…
- 在 Vue 3 这块的支持,我们这边也是贡献了自己的力量,例如,对于
mixins
和extend
继承相关类型支持,感兴趣的同学可以参考 github.com/vuejs/vue-n… - 整体上关于 Vue 3 中的 defineComponent 相关的一些在类型上的支持所做的事情,可以参考 juejin.cn/post/699461…
希望可以对你有所帮助。