学习 TS 的原因
对比 ts 和 js
首先 ts 是 js 的超集,可以很好的解决大项目代码的复杂性。同时 ts 是一种强类型语言,因此对于确定类型的变量是不允许随便修改数据类型的,且能够在编译时就发现代码的错误,而 js 可以将变量随意赋值,且只能在运行时发现错误。
ts 的好处
- 类型安全: 因为 ts 是一个强类型语言,不能随意修改确定的变量类型
- 特性支持好:会支持 ES 新版本里面才支持的功能特性
- 工具链完善:因为 ts 带来的强类型,基于这个特性我们更好地理解源码,从而整理开发工具,提高开发效率
TS 基础
基础类型
- boolean (布尔类型)、 number(整数、复数、浮点数)、string(字符类型)
- enum(枚举类型):定义数据集合,可以为 数字和字符串,也可以用 const 来修饰成常量
普通枚举:若不设置属性值,则默认值会从 0 开始递增,若设置则为设定值
enum A {
a, // 0
b, // 1
c = 3, // 3
d, // 4
}
- any、unknown、void
any 不会被检测,允许任何操作, unknow 只能被赋值而不能被反向赋值
- never:用于防御性编程,表示永远不存在值的类型
- [ ]:(数组类型)
- tuple(元组类型):是数组类型的特殊形式,必须声明数组每一项的类型
let arr: [string, number, boolean] = ['1', 1, true]
函数类型
TS 在定义函数类型时需要定义输入参数的类型和输出类型
输入参数:可以是可选参数和默认参数
输出参数:输出可以自动判断,若没有返回值则为 void 类型
函数重载:对于函数名称相同但是传入参数类型不同的可以支持函数重载
function add(x: number[]): number
function add(x: string[]): string
function add(x: any[]): any {
if (typeof x[0] === 'string') {
return x.join()
}
if (typeof x[0] === 'number') {
return x.reduce((acc, cur) => acc + cur)
}
}
interface
用于定义对象类型,多数情况下用于描述函数类型
interface Test{
// 只读属性
readonly id: string
name: string
age: number
hobby: () => string
// 可选属性
sex?: boolean
// 自定义属性
[propname:string]: string
}
类 Class
相比于 JS 增加了一些定义,如 public、private、protected 修饰符
通过 abstract 申明抽象类,抽象类不能被实例化,方法必须被子类实现
类的继承
使用 extends 关键字实现继承
子类中使用 super 关键字来调用父类的构造函数和方法
class Animal {
public name: string;
constructor(name: string) {
this.name = name;
}
say(): string {
return 'Hello World';
}
}
class Cat extends Animal {
constructor(name) {
// 子类调用父类的构造函数
super(name);
}
say(): string {
// 子类调用父类的方法
return 'Cat, ' + super.say();
}
}
存取器
可以通过 getter、setter 来改变类中属性的读写行为
class Person {
name: string
constructor(name: string) {
this.name = name
}
get getName() {
return this.name
}
set setName(val: string) {
this.name = val
}
}
TS 进阶
高级类型
联合类型 |
let a: number | string
a = 1
a = 'a'
交叉类型 &
当交叉类型的属性有重复时,将会导致该属性的类型为 never
- 类型断言:可以使用 as 关键字实现属性前置的断言定义
- 类型别名 type
相比于 interface,两者都可以定义对象或者函数,且允许继承
区别是 interface 是 TS 用于定义对象,type 是方便定义别名使用,interface 在重复声明时会合并,而 type 不行
泛型
语法:一般用 T 表示参数类型,写在 <> 中
可以支持多种类型的数据,高度可重用性
function print<T>(arg: T): T {
console.log(arg);
return arg
}
print('hello') // hello
print(1) // 1
作用:临时占位,之后通过传入的参数类型来进行自动推导
基本操作符
- typeof:获取类型
- keyof: 获取所有键
interface Person {
name: string
age: number
}
type k = keyof Person // 'name' | 'age'
- in:遍历枚举类型
type keys = "a" | "b" | "c"
type Obj = {
[p in keys]: any
} // => {a: any, b: any, c: any}
- T[K]:索引访问
interface Person {
name: string
age: number
}
let type1: Person['name'] // string
- extends:泛型约束
interface Length {
length: number
}
function lengthDefine<T extends Length>(arg: T): T {
return arg;
}
// 因为定义了 Lengtb 的参数为 length
lengthDefine({length: 5})
工具类型
-
Partial:将类型属性转为可选
[P in keyof T]通过映射类型,遍历 T 上的所有属性?设置为属性为可选的T[P]设置类型为原来的类型
type Partial<T> = {
[P in keyof T]?: T[P]
}
- Required:将类型属性转为必选
type Partial<T> = {
[P in keyof T]-?: T[P]
}
- Readonly:将类型属性转为只读
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}
- Pick: K 中 P 的属性类型设置为 T 中 P 的属性类型
type Pick<T, K extends keyof T> = {
[P in K]: T[P]
}
- Record:构造一个 type,key 为联合类型中的每个子类型,类型为 T
type Record<K extends keyof any, T> = {
[P in K]: T
}
/**
* @example
* type Eg2 = {a: B, b: B}
*/
interface A {
a: string,
b: number,
}
interface B {
key1: number,
key2: string,
}
type Eg2 = Record<keyof A, B>
- Exclude:构建一个联合类型其类型在 T 中而不包含在 U 中
type Exclude<T, U> = T extends U ? never : T
- Extract:构建一个联合类型其类型取 T 和 U 的交集
type Extract<T, U> = T extends U ? T : never
- Omit:从 T 中删除 K 包含的类型
// method 1
type Omit = Pick<T, Exclude<keyof T, K>>
// method 2
type Omit2<T, K extends keyof any> = {
[P in Exclude<keyof T, K>]: T[P]
}
TS 实战
声明文件
- declare:三方库需要类型声明文件
- @types:三方库 TS 类型包
- .d.ts:声明文件定义
- tsconfig.json:定义 TS 配置
泛型约束后端接口类型
import axios from 'axios'
interface API {
'/book/detail': {
id: number
}
}
function request<T extends keyof API>(url: T, obj: API[T]) {
return axios.post(url, obj)
}