Typescript 声明合并的一些使用
vue
的Typescript支持
文章中,提到一个增强类型以配合插件使用
我们的代码中也经常出现这些,原理就是基于Typescript
的申明合并(Declaration Merging)
特性
Declaration Merging
声明合并
先贴一波描述
TypeScript中有些独特的概念可以在类型层面上描述JavaScript对象的模型。
这其中尤其独特的一个例子是“声明合并”的概念。 理解了这个概念,将有助于操作
现有的JavaScript代码。 同时,也会有助于理解更多高级抽象的概念。
对本文件来讲,“声明合并”是指编译器将针对同一个名字的两个独立声明合并为
单一声明。 合并后的声明同时拥有原先两个声明的特性。 任何数量的声明都可被
合并;不局限于两个声明。
下图声明了哪些类型相互间不可以声明合并
Declaration Type | Namespace | Type | Value |
---|---|---|---|
Namespace | X | X | |
Class | X | X | |
Enum | X | X | |
Interface | X | ||
Type Alias | X | ||
Function | X | ||
Variable | X |
我们来罗列一些常用的场景
常见的场景
日常开发中,常见的使用申明合并(Declaration Merging)
场景有
当使用的依赖不是ts
编写的或者没有typings
申明文件package.json中没有typings
如果
@types/package-name
存在,可以安装@types/package-name
来添加这个依赖的ts
声明1
npm install @types/package-name --save-dev
如果没有
@types/package-name
,可以自己手写声明文件,为这个库添加ts
声明1
2
3
4
5
6
7
8
9// js-cookie.d.ts
declare module "js-cookie" {
interface cookie{
get(key:string):string?
// ....
}
const Cookie:cookie
export default Cookie
}为我们现有的依赖添加自定义属性或方法的声明
开发过程中我们有时候会为一些依赖添加一些属性或者方法,为了能全局感知,我们可以为当前的依赖添加一个属性额外声明
比如vue
官网的这个例子:
为vue
声明一个string
类型的实例property
$myProperty
来源:1
2
3
4
5
6
7
8
9
10
11// 1. 确保在声明补充的类型之前导入 'vue'
import Vue from 'vue'
// 2. 定制一个文件,设置你想要补充的类型
// 在 types/vue.d.ts 里 Vue 有构造函数类型
declare module 'vue/types/vue' {
// 3. 声明为 Vue 补充的东西
interface Vue {
$myProperty: string
}
}vuex
的类型系统虽然有些小毛病,但是一些小小的操作也能为我们的代码开发带来幸福感
我们可以尝试为vuex
的mapState
添加类型支持假设我们已经为
vuew
的Store
中的rootState
做了如下类型声明:1
2
3
4interface RootState{
userName:string
userId:number
}我们可以为
mapState
这个方法添加如下声明1
2
3
4
5declare module 'vuex/types/helpers' {
interface Mapper<R> {
<Key extends keyof RootState>(map: Key[]): { [K in Key]: () => RootState[K] }
}
}这时候在你的
vue
代码中使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import { mapState } from 'vuex'
const states = mapState(['userName','userId'])
// 会看到类型有了提示
// states的类型为 => { userName: () => string; userId: () => number; }
export default {
computed:{
...mapState(['userName','userId'])
},
methods:{
test(){
this.userName // => string
this.userId // => number
}
}
}虽然我们不能改变
vue
中$store
的类型,但是通过这个方法,我们可以把mapState
的结果变为我们预期的呀mapActions
和mapMutations
等我们也可以像这样去修改声明一些全局的方法变量
代码中经常会有一些自定义的全局变量或者方法,用着用着就忘了是什么类型了,用着用着就忘了该传什么参数了。
为这些对象添加申明,这样以后每次只要记得名字就好了申明一次,提示永久
比如:声明window.imagePreview
1
2
3
4
5
6
7
8
9
10import { ImagePreviewOptions } from 'vant'
declare global {
interface Window {
/**
* 预览图片
*/
imagePreview(option: ImagePreviewOptions, startPosition?: number): void
}
}声明环境变量变
process.env
经常用到,但又不是天天用,每个项目还定义不一样
这时候为process.env
添加一个申明就变得有意义了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20declare global {
namespace NodeJS {
interface ProcessEnv extends Dict<string> {
/**
* 当前环境
* - `local` 本地开发环境
* - `production` 正式环境
* - `dev` 测试环境
* - ....
*/
CODE_ENV: 'local' | 'production' | 'dev'
/**
* 可以判断当前`vue-cli-service` 的参数
* - `development` -> `vue-cli-service serve`
* - `production` -> `vue-cli-service build`
*/
NODE_ENV: 'development' | 'production'
}
}
}改变一些代码组织结构
一般我们定义接口声明的时候,会一起定义依赖的参数和返回,大概就是下面这个样子
有一个小小的尴尬,随着接口的增加,这个文件下面export出来的东西就越来越多1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28// #region 查询用户列表
/**
* 获取用户信息返回
*/
export interface GetUserListResponse {
/**
* 接口状态码
*/
code: number
/**
* 接口返回信息
*/
message?: string
/**
* 返回的数据
*/
data: any
}
export interface GetUserListQuery {
userName?: string
}
/**
* 查询用户列表
*/
export function getUserList(queryParam: GetUserListQuery): Promise<GetUserListResponse> {
return {} as any
}
// #endregion借助
function
和namespace
可以申明合并,也可以写成下面这个样子
这样我们每个接口就只会暴露出一个命名空间1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29/**
* 查询用户列表
*/
export function getUserList(queryParam: getUserList.Query): Promise<getUserList.Response> {
// @todo
return {} as any
}
export namespace getUserList {
/**
* 查询用户列表
*/
export interface Response {
/**
* 接口状态码
*/
code: number
/**
* 接口返回信息
*/
message?: string
/**
* 返回的数据
*/
data: any
}
export interface Query {
userName?: string
}
}当然,声明合并的用途还很多,我也只是一个
typescript
小学生,若有不正之处,还请多多指教