简单讲,就是通过整套的类型规则和编译推断,帮你提前避免代码中可能出现的错误
所以写TS时必须要有完整的代码思路,从上至下整理好。
写法都是固定的,思路最重要,编程本身就是程序员思路的体现
TS的思路构建与理解
前端项目中,涉及到TS的部分,可以粗略划分为以下几大部分。
- 1.接口请求部分
- 2.根组件、菜单、路由等基础设置部分
- 3.功能组件、业务组件部分
要针对性的设计不同的类型规范,来避免代码可能造成的错误。
接口请求部分,我们可以规定请求参数的格式如下:
type OptionParams = Record<string, string | number | null | undefined | unknown>
const GetDataListApi = <T>(params: OptionParams) => {
return request<T>({
url: '/api/text/1',
method: 'GET',
params
})
}
interface AxiosRequest<T = unknown> {
code: number
msg: string
data: T
}
export function request<T>(options: RequestBody): Promise<AxiosRequest<T>> {
const {
method, url, data, params, toast, headers, responseType } = options
return new Promise((resolve, reject) => {
axios({
method: method || 'GET',
url,
data,
params,
headers,
responseType
}).then(res => {
resolve(res.data as unknown as AxiosRequest<T>)
}).catch(error => {
reject(error.data)
})
})
}
Record 是 TypeScript 内置的一个工具类,它用于创建一个对象类型。Record<K, T>,其键是类型 K,值是类型 T。
type 关键字用于定义类型别名(Type Aliases)。类型别名允许你为一个类型创建一个新的名称。
- 这里我们就通过定义类型OptionParams,规定了接口参数的格式:
必须是对象,键为字符串,值可以是string | number | null | undefined | unknown其中一种 - 通过定义接口AxiosRequest,规定了接口响应体的格式。
code: number 类型的状态码
msg: string 类型的响应信息
data: T unknown类型也就是任意类型的响应数据。- unknown 接受任何类型的值
- unknown 赋值给其他类型受限会报错(除了 any 和 unknown)。
- unknown 执行大多数操作都不被允许,除非先进行类型断言或类型保护。
- 相比any直接绕过TS编译时的类型检查,unknown 可以最大限度在顶层对类型作出限制。
子组件内,我们也可以规定请求参数的格式:
import {
ElTableColumn } from 'element-plus'
type ApiRequest = <T>(params: OptionParams) => Promise<AxiosRequest<T>>
interface QueryParamsMap {
[key: string]: string | number | boolean | unknown
}
interface QueryParams {
api?: ApiRequest
params?: QueryParamsMap
}
type TableColumn = Partial<InstanceType<typeof ElTableColumn>>
interface TableData {
[key: string]: string | number | boolean | unknown | TableData
}
interface PaginationRequest<T> {
totalElements: number
totalPages: number
size: number
number: number
content: T
}
interface CustonizedTableProp {
title: string
tableColumns: TableColumn[]
slots?: string[]
queryParams?: QueryParams
canExport?: boolean
exportApi?: ApiRequest
beforeRequest?: (params: QueryParamsMap) => void
}
const props = defineProps<CustonizedTableProp>()
泛型<T>,就是类型占位符。调用函数时将实际指定的参数类型链式传递给参数类型和返回值类型。 function identity<T,U>(value:T ,message:U):T{ return value }; identity<number,string>(68,'跳动的世界线');
-
这里我们就通过定义接口QueryParams,规定了传入子组件的属性之一的参数格式:
可选api参数,要符合ApiRequest的类型定义
可选params参数,要符合QueryParamsMap的类型定义
前者是传入子组件的接口,后者是该接口请求发起需要的参数 -
定义类型TableColumn,取自element-plus的类型,引用该类型后可以直接使用内部属性。例如:
const tableColumns = [{ label: ‘日期’, prop: ‘summaryDate’, minWidth: 160 }] -
定义接口TableData,规范传入el-table内数据的类型结构
-
定义接口PaginationRequest,规范接口返回时的数据类型
对于JS常用的写法改变如下:
- 对于类型定义
可以用type来设置类型别名
或是使用interface来定义对象的结构与类型。相比起类型别名,接口功能更强大。且相比JS可以省去变量申明,一次将类型定义和变量申明全部搞定,类似CustonizedTableProp 。- 合并:你可以声明多个同名的接口,TypeScript 会将这些接口自动合并为一个单一的接口。
- 扩展:接口可以通过 extends 关键字来继承其他接口。
- 主要用于描述对象的形状,通常用于类、对象字面量等。类似CustonizedTableProp
- 不能直接用于描述基本类型(如 string, number 等)。
- 对于常规变量
可以直接添加类型标注let i:number
来规定其类型,
或是使用联合类型let i:number|string
枚举类型let i:1|2|3
或是直接定义类型别名type numStr = number|string; function F(userId:numStr){}
- 对于函数
可以定义泛型函数。在函数名后加<T>
来指定类型,使用时再传入。
// 定义一个泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 使用泛型函数并指定类型参数
const result1 = identity<string>("hello");
const result2 = identity<number>(42);
- as断言
可以配合unknown使用,告诉TS这里可以断定是某种类型,再以此类型进行程序编译。 - 基础数据类型
- number 所有数值类型,包括整数和浮点数
- string 字符串类型
- boolean 布尔值 true 和 false
- null 空值
- undefined 未定义的值
- never 表示永远不会发生的值,通常用于抛出异常或无限循环的函数
- void 表示没有返回值的函数
- object 表示非原始类型的对象
- any 绕过类型检查
- unknown 表示未知类型的值,比 any 更安全,需要进行类型断言才能使用。
- 复杂类型和其他类型
-
数组 (Array 或 T[]) 表示特定类型的数组。
let numbers: number[] = [1, 2, 3]
let mixed: Array<number | string> = [1, "two", 3]
-
元组 ([type1, type2, …]) 表示固定长度和类型的数组。
let point: [number, number] = [10, 20];
-
函数类型,定义函数的参数和返回值类型。
type AddFunction = (a: number, b: number) => number
const add: AddFunction = (x, y) => x + y
-