特别说明:本项目只关注如何写可用 ts 类型,其他诸如:根路径配置类型文件、ts 类型查找顺序、ts 类型体操等均不涉及
项目中需要使用 ts 类型的文件及其结构
1 | # 文件名及文件层级 |
先看项目中出现过的几个 ts 典型写法
*.vue
文件内
1 | // some-detail.vue script.lang = ts |
总结
1. 频繁使用 ts 类型的几个地方
server/api.ts
接口定义,接口通用 axios 实例类型返回动态类型store/modules
state 定义,融合多个模块的类型router/mpdules
routes 定义,扩展字段类型- 全局类型定义:扩展原模块类型
*.vue
文件内setup
&composables/*.ts
逻辑组件, 内部:函数声明,变量定义,赋值操作等
2. 文件内频繁写错方向的类型:使用推导类型
- 凡是赋值操作,赋值右边带有类型推导的,类型一律由右边决定,不必在左边写
any
- 凡是简单类型赋值操作,ref内部的,都不必声明类型
const a = ref('some str'); // a 一定会被推导为 Ref<string>
- 函数操作(useStore/useRoute/数组操作(map,forEach,for(of,in))/自定义函数,此处只是执行操作的函数),类型由函数定义时返回的参数类型决定
1 | // 以上写法可以改成? |
3. 定义函数/变量时如何返回需要的类型
扩展全局模块类型: router/store/axios
衍生问题,如何知道 router 上有 RouteMeta 类型?(或者说如何知道 Router 有哪些类型)
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// 扩展全局模块类型router/store/axios,以router为例
/**
* 模块导出的2种方式
*/
// 方法一:全局导出声明
// 确保是模块
// export {}
// declare module 'vue-router' {
// // 自定义元字段声明
// interface RouteMeta {
// // is optional
// isAdmin?: boolean
// requiresAuth?: boolean,
// // must be declared by every route
// title?: string
// }
// }
// 方法二:模块导出声明
declare interface RouteMeta {
// is optional
isAdmin?: boolean
requiresAuth?: boolean
// must be declared by every route
title: string
}composables/directives/server
Method 是什么类型?
如何修改 request 的定义,使得可以在使用getData
是返回与后端确定好的结构?在修改过程中碰到哪些问题?请举例
1 | // server/index |
如何在快速迭代的项目中书写正确可用的类型
1. 渐进式类型更新:比 any 更好一点的写法
引用类型reactive
1 | // 一开始可能只知道某个变量的几个属性,大致结构,不确定后续属性的新增/减少,可以这样写 |
原始类型:联合类型、交叉类型、字面量类型
1 | // 字面量类型 |
把类型当作值的集合,理解 类型中的 union type - 交集 和 intersection type -并集
1 | type AA = {aa: 'A'} |
2. 项目中需要的类型基本上可由:联合类型(|)、交叉类型(&)、 字面量类型、泛型 以及枚举覆盖,不需要类型体操
从以上例子中考虑:
联合类型
和交叉类型
的区别? 可以得出交叉类型的应用场景是什么?
实际上,项目中使用最常见的还是联合类型和单一的type/interface, 使用 交叉类型 较多的场景是 store
,为什么?
项目中常见的需要利用泛型的几种类型:new Promise
, new Map
, new Set
等
1 | new Promise<T>((resolve, reject) => { |
进阶:类型越来越多,每新增一个文件、每新增一个api、每新增一个函数都会面临类型不够用的情况
1. 梳理项目中的类型分类,文件结构
考虑众多的类型定义,引用,需要对所有类型进行划分,文件结构梳理,有哪几种方式?考虑以上涉及到的文件和使用地方,类型导出声明的几种方式,类型分布项目中的文件结构可能有哪几种?
2. 先提炼类型最多,最频繁使用的文件:server/api.ts
, store/modules
, components/*.vue
提炼相近的结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 以api.ts为例:前置条件为 基础方法都已经封装完毕,直接使用即可:get, post, put等
import { post, get } from 'path/to/server/index'
// 管理后台项目考虑众多的list接口返回的结构基本都是一致的,所以以下泛型其实并不需要
const getList = <T>(data: {idList: string[]}) => {
return get<T>('/path/to/get/list', data)
}
// 修改如下
import { ListReturnType } from '/path/to/interfaceAPI'
const getList = <ListReturnType>(data: {idList: string[]}) => {
return get<ListReturnType>('/path/to/get/list', data)
}
// interfaceAPI.ts
export interface ListReturnType {
records: string[] | number[] | Record<string, unknown>[]
page: number
total: number
}使用泛型,先不考虑合理的问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 考虑一种情况,返回类型由参数类型决定,此时最简单的就是泛型
import { post, get } from 'path/to/server/index'
const getListTypeByParamsType = <T>(data: T) => {
return get<T[]>('/path/to/get/list', data)
}
// 在组件A.vue中使用:一个异步函数中获取返回值的类型,以便后续的操作
const getListFinallyA = async () => {
// getListTypeByParamsType: number[]
const getNumberList = await getListTypeByParamsType<number>()
const handleNumList = getNumberList.filter(v => v > 1000)
}
// 在组件B.vue中使用
const getListFinallyB = async () => {
// getStringList: string[]
const getStringList = await getListTypeByParamsType<string>()
const handleStrList = getStringList.map(v => v.length)
}将所用类型分类拆分,大量使用联合类型、交叉类型
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60// 考虑一种实际业务情况,一位客户的公开信息由:基础信息 & 会员信息 & 订单信息组成
// 对应的接口在不同页面或不同条件下返回的可能会只有其中1种或多种,而且只能在页面使用时才能确定类型,那么此时的接口返回类型怎么写比较好呢?
// interfaceAPI.ts
export type GenderType = 'f' | 'm' | 1 | -1 | 2; // 考虑兼容情况
export interface GoodsType {
name: string
amount: number
id: string
type: string
}
export interface BaseInfo {
nickName: string
anyAge: number
gender: GenderType
children?: BaseInfo[]
}
export interface MemberInfo {
level: 1 | 2 | 3 | 4 | 5
numMember: string | number
bonusPoint: number
timesConnected: number
}
export interface OrderInfo {
channels: 'jd' | 'tmall' | 'pdd' | 'amazon'
totalAmount: number
purchasedGoods: GoodsType[]
}
// api.ts
import { get } from 'path/to/server/index'
const getCustomerInfo = <T>(data: { id: string; type?: number }) => {
return get<T>('/path/to/get/list', data)
}
const getCustomerInfoA = (id: string) => {
return getCustomerInfo<BaseInfo>({id, type: 0})
}
const getCustomerInfoB = (id: string) => {
return getCustomerInfo<BaseInfo & MemberInfo>({id, type: 1})
}
const getCustomerInfoC = (id: string) => {
return getCustomerInfo<BaseInfo & MemberInfo & OrderInfo>({id, type: 2})
}
const resA = getCustomerInfoA('123').then((res) => {
const { nickName } = res
console.log(nickName)
})
const resB = getCustomerInfoB('123').then((res) => {
const { nickName, level } = res
console.log(nickName, level)
})
const resC = getCustomerInfoC('123').then((res) => {
const { nickName, level, purchasedGoods } = res
console.log(nickName, level, purchasedGoods)
})使用部分联合类型时,考虑用
Pick
Omit
等方法
1 | // 可选 |
部分答案,仅供参考,如有错误,概不负责,请自行甄别
1 | // some-detail.vue script.lang = ts |