# TS
# 定义数据类型
| |
| * 定义数据类型 |
| */ |
| let num: number = 1; |
| let bool: boolean = false; |
| |
| * 定义固定类型数组 |
| */ |
| let arr1: number[] = [1, 2, 3]; |
| let arr2: string[] = ["1", "2"]; |
| |
| * 使用泛型定义固定类型数组 |
| */ |
| let arr3: Array<number> = [1, 2, 3]; |
| |
| * 不声明类型定义数组 |
| */ |
| |
| |
| let arr4 = [1, 2, 3]; |
| let arr5 = [1, "2"]; |
| |
| * 定义混合类型 any 数组 |
| */ |
| |
| let arr6: any[] = [1, "ds", false]; |
# 元组
| |
| * 元组(类型写在数组外面,元组类型写在数组里面) |
| */ |
| |
| let arr7: [number, boolean] = [1, false]; |
| |
| arr7.push(2); |
| |
# 联合类型
| |
| * 联合类型 |
| */ |
| let a: number | string = 0; |
# 字面量类型
| |
| * 字面量类型(字面量类型声明的值中只能赋值为指定的) |
| */ |
| let a1: 1 | 0 = 0; |
# 枚举类型
enmu 类型代表选取多个中的一个,枚举类型相当于对象和类型的结合,既可以当变量使用,也可以作为类型进行约束。可以使用 enum 约束变量的类型,限制使用函数时只能传入 enum 中的属性。
例如:
| enum STATUS { |
| OPEN = 'OPEN', |
| CLOSE = 'CLOSE', |
| } |
| const clickSwitch = (current: STATUS) => { |
| return '测试' |
| } |
| clickSwitch(STATUS.OPEN) |
基本使用:
| |
| * 枚举类型 enum |
| */ |
| enum Color { |
| red, |
| green, |
| blue, |
| } |
| let a2 = Color.blue; |
| console.log(a2); |
| enum Color { |
| red1 = 1, |
| green1, |
| blue1, |
| } |
| console.log(Color.blue1); |
| enum Color { |
| red2 = "red", |
| green2 = "green", |
| blue2 = "blue", |
| } |
| console.log(Color.blue2); |
# any 和 unknown 类型
| |
| * any 类型和 unkonwn 类型的区别 |
| * unkonwn 类型表示强制类型检查(可以通过赋值改变类型,只有类型明确时才能使用对应属性或方法) |
| * any 类型表示不进行类型检查 |
| */ |
| let a3: unknown = 1; |
| a3 = "1"; |
| a3 = { a1: "dsf" }; |
| |
| |
| |
| if (typeof a3 === "function") { |
| a3(); |
| } |
| if (typeof a3 === "string") { |
| a3.toUpperCase(); |
| } |
# void、undefined 和 never 类型
| |
| * void 类型、undefined 类型 和 never 类型 |
| * void 类型一般用于表示函数无返回值 |
| * undefined 类型一般用于表示函数直接 return 或者 return undefined |
| * never 类型一般用于表示函数死循环,永远无法 return |
| */ |
| |
| function a4(): undefined { |
| console.log("a4"); |
| return; |
| } |
| |
| function a5() { |
| console.log("a5"); |
| } |
| |
| function throwErr(message: string, errCode: number): never { |
| throw { |
| message, |
| errCode, |
| }; |
| } |
| function whileLoop(): never { |
| while (true) { |
| console.log("永远为真"); |
| } |
| } |
# 类型断言
| |
| * 类型适配(类型断言)as |
| */ |
| let message: any; |
| message = "1cf"; |
| let a6 = (<string>message).endsWith("c"); |
| let a7 = (message as string).endsWith("f"); |
# 函数的声明
| |
| * 函数的声明 |
| */ |
| |
| let a8 = (message: string, code: number) => { |
| console.log(message, code); |
| }; |
| a8("a8", 8); |
| |
| let a9 = (message: string, code?: number) => { |
| console.log(message, code); |
| }; |
| a9("你好"); |
| |
| let a10 = (message: string = "你好", code: number = 1) => { |
| console.log(message, code); |
| }; |
| a10("嗨"); |
# 具体对象类型
| |
| * 具体对象类型 |
| */ |
| let person1: { |
| name: string; |
| age: number; |
| } = { |
| name: "张三", |
| age: 18, |
| }; |
| let person2 = { |
| name: "李四", |
| age: 20, |
| }; |
| |
| let person3: any = { name: "王五", age: 22 }; |
| console.log(person3.nickname); |
# 接口 interface
| |
| * 定义接口 interface |
| */ |
| interface Point { |
| x: number; |
| y: number; |
| } |
| let drawPoint = (point: Point) => { |
| console.log({ x: point.x, y: point.y }); |
| }; |
| |
| interface Person { |
| age: number; |
| name: string; |
| say: () => void; |
| } |
# 类和接口
| |
| * 类 class (ES6 中也有类的概念) |
| */ |
| class PersonClass implements Person { |
| |
| age: number; |
| name: string; |
| constructor(age: number = 10, name: string = "张三") { |
| |
| this.age = age; |
| this.name = name; |
| } |
| say = () => { |
| console.log(`我是:${this.name},今年${this.age}碎了`); |
| }; |
| } |
| const person4 = new PersonClass(); |
| person4.say(); |
# 类的访问修饰符
| |
| * 构造函数中访问修饰符:public、private、protected |
| */ |
| |
| class PersonClass2 implements Person { |
| |
| |
| constructor(public age: number, public name: string) { |
| |
| |
| |
| } |
| say = () => { |
| console.log(`我是:${this.name},今年${this.age}碎了`); |
| }; |
| } |
| const person5 = new PersonClass2(100, "活活"); |
| person5.say(); |
| |
| interface pp { |
| say: () => void; |
| |
| getAge: () => number; |
| getName: () => string; |
| setAge: (val: number) => void; |
| setName: (val: string) => void; |
| } |
| class PersonClass3 implements pp { |
| |
| constructor(private age: number, private name: string) { |
| |
| |
| } |
| say = () => { |
| console.log(`我是:${this.getName},今年${this.getAge}碎了`); |
| }; |
| |
| getName = () => { |
| return this.name; |
| }; |
| getAge = () => { |
| return this.age; |
| }; |
| setName = (val: string) => { |
| this.name = val; |
| }; |
| setAge = (val: number) => { |
| this.age = val; |
| }; |
| } |
# 泛型 <T>
# 泛型是什么
泛型是程序设计语言的一种风格或范式,它允许我们使用一些以后才指定的类型。在定义函数接口或者类时,不预先定义好具体的类型,而在使用的时候才去指定类型的一种特性。
| |
| * 泛型 & lt;T> |
| * 泛型定义了函数中需要使用的类型(可以动态变化,写在变量名之后,括号之前) |
| * 不止函数,类和接口中都可以使用泛型 |
| */ |
| |
| let lastInArray = <T>(arr: Array<T>) => { |
| return arr[arr.length - 1]; |
| }; |
| const L1 = lastInArray([1, 2, 3]); |
| console.log(L1); |
| const L2 = lastInArray(["a", "b"]); |
| console.log(L2); |
| |
| let L3 = lastInArray<string | number>(["1", "2"]); |
| |
| let L4 = <T, Y>(x: T, y: Y) => [x, y]; |
| const v1 = L4(1, "one"); |
| |
| interface ReturnItemFn<T> { |
| (para: T): T; |
| } |
| const returnItem: ReturnItemFn<number> = (para) => para; |
| |
| |
| class Stack<T> { |
| private arr: T[] = []; |
| |
| public push(item: T) { |
| this.arr.push(item); |
| } |
| |
| public pop() { |
| this.arr.pop(); |
| } |
| } |
| const stack = new Stack<number>(); |
# 类型 type
| |
| * 类型 type |
| */ |
| type Square = { |
| size: number; |
| }; |
| type Rectangle = { |
| width: number; |
| height: number; |
| }; |
| type Shape = Square | Rectangle; |
| function area(shape: Shape) { |
| if ("size" in shape) { |
| |
| return shape.size ** 2; |
| } |
| if ("width" in shape) { |
| return shape.width * shape.height; |
| } |
| } |
# 类型守护 is
类型守护的意义就在于 类型的倒推
,由结果推导出变量的具体类型(收缩变量的类型范围),这样当结果已经知道时,就可以推导出变量为 unknow 或其他等一些未知的类型。
例如:
当变量使用 unknown 类型,判定结果为 boolean 类型时,不使用 as 关键字就无法在后面使用 value 对应的方法,因为它的类型是 unknown。
| const isString = (value: unknown): boolean => typeof value === "string"; |
| function test(value: unknown): string { |
| if (isString(value)) { |
| |
| (value as string).charCodeAt(0); |
| } |
| return ""; |
| } |
使用 is 类型守护,当结果类型已知时,就可以倒推出变量的类型(收缩类型),进而可以使用对应的方法
| const isString = (value: unknown): value is string => typeof value === "string"; |
| function test(value: unknown): string { |
| if (isString(value)) { |
| value.charCodeAt(0); |
| } |
| return ""; |
| } |
使用:
| |
| * 类型守护 |
| * 类型判断:typeof, 实例判断:instanceof,属性判断:in,字面量相等判断 (== 等) |
| * 自定义类型判断,通过:{形参} is {类型} |
| */ |
| |
| type Square1 = { |
| size: number; |
| }; |
| type Rectangle1 = { |
| width: number; |
| height: number; |
| }; |
| function isSquare(shape1: Shape1): boolean { |
| return "size" in shape1; |
| } |
| function isRectangle(shape1: Shape1): boolean { |
| return "width" in shape1; |
| } |
| type Shape1 = Square1 | Rectangle1; |
| function area1(shape1: Shape1) { |
| if (isSquare(shape1)) { |
| |
| return shape1.size ** 2; |
| } |
| if (isRectangle(shape1)) { |
| return shape1.width * shape1.height; |
| } |
| } |
| |
| * 应该将判断函数定义为类型(而不是布尔值),这样类型守卫才不会失效 |
| */ |
| type Square1 = { |
| size: number; |
| }; |
| type Rectangle1 = { |
| width: number; |
| height: number; |
| }; |
| function isSquare(shape1: Shape1): shape1 is Square1 { |
| |
| return "size" in shape1; |
| } |
| function isRectangle(shape1: Shape1): shape1 is Rectangle1 { |
| return "width" in shape1; |
| } |
| type Shape1 = Square1 | Rectangle1; |
| function area1(shape1: Shape1) { |
| if (isSquare(shape1)) { |
| |
| return shape1.size ** 2; |
| } |
| if (isRectangle(shape1)) { |
| return shape1.width * shape1.height; |
| } |
| } |
# 函数重载
| |
| * 函数重载(TS 的函数重载发生在编译时而不是运行时,因为 js 不支持函数重载) |
| */ |
| |
| function makeDate(timeStamp: number): Date; |
| function makeDate(year: number, month: number, day: number): Date; |
| function makeDate(timeStampOrYear: number, month?: number, day?: number) { |
| if (month != null && day != null) { |
| return new Date(timeStampOrYear, month - 1, day); |
| } else { |
| return new Date(timeStampOrYear); |
| } |
| } |
| |
| console.log(makeDate(123923892)); |
# 函数签名
| |
| * 调用签名(签名是函数重载中的概念,描述了函数所需的参数类型和返回值类型) |
| */ |
| |
| type Add = { |
| (a: number, b: number): number; |
| debugName: string; |
| (a: number, b: number, c: number): number; |
| }; |
| const add: Add = (a: number, b: number, c?: number) => { |
| return a + b + (c != null ? c : 0); |
| }; |
| add.debugName = "附加信息"; |
| |
| * new 构造函数进行签名 |
| */ |
| type Poit = { |
| new (x: number, y: number): { x: number; y: number }; |
| new (x: number, y: number, z: number): { x: number; y: number; z: number }; |
| }; |
| const point = class { |
| constructor(public x: number, public y: number, public z?: number) {} |
| }; |
| |
| * 索引签名 |
| */ |
| |
| type Dictionary = { |
| [key: string]: any; |
| }; |
| |
| type Person1 = { |
| name: string; |
| age: number; |
| }; |
| |
| type PersonDictionary = { |
| [username: string]: Person1; |
| }; |
| const persons: PersonDictionary = { |
| alex: { |
| name: "阿莱克斯", |
| age: 18, |
| }, |
| michal: { |
| name: "jackson", |
| age: 20, |
| }, |
| }; |
| const alex = persons["alex"]; |
| persons["张三"] = { |
| name: "张三", |
| age: 100, |
| }; |
| delete persons["张三"]; |
| |
| const aa = persons["不存在"]; |
| |
# 函数副作用
| |
| * 函数的副作用 |
| */ |
| function reverseSorted(input: number[]): number[] { |
| return input.sort().reverse(); |
| } |
| let arr8 = [1, 2, 3, 4]; |
| const result = reverseSorted(arr8); |
| console.log(result, arr8); |
# 函数参数修饰符
| |
| * 修饰符:public,private,readonly,protected |
| */ |
| |
| * readonly 只读属性 |
| */ |
| |
| function reverseSorted2(input: readonly number[]): number[] { |
| |
| return [...input].sort().reverse(); |
| } |
| let arr9 = [1, 2, 3, 4]; |
| const result2 = reverseSorted2(arr9); |
| console.log(result2, arr9); |
# 双重断言
| |
| * 双重断言 |
| */ |
| |
| type T2D = { x: number; y: number }; |
| type T3D = { x: number; y: number; z: number }; |
| let P2D: T2D = { x: 1, y: 1 }; |
| let P3D: T3D = { x: 10, y: 10, z: 10 }; |
| P2D = P3D; |
| |
| P3D = P2D as T3D; |
| |
| type PersonT = { name: string; age: number }; |
| let person6: PersonT = { name: "张三", age: 18 }; |
| |
| |
| P3D = person6 as any as T3D; |
# 字符串不可更改
| |
| * js 中的字符串不可更改 immutable |
| */ |
| let king = "elvis"; |
| king = "james"; |
| king[2]; |
| |
# 常量断言 as const
| |
| * 常量断言(js 中对象类型的值可以修改) |
| */ |
| const obj = { |
| name: "张三", |
| age: 19, |
| }; |
| obj.name = "李四"; |
| const obj2 = { |
| name: "王五", |
| age: 100, |
| arr: [1, 2, 3], |
| } as const; |
| |
| |
| |
| function layout(setting: { align: "left" | "center" }) { |
| console.log("align为:", setting.align); |
| } |
| |
| |
| const test = { align: "left" } as const; |
| layout(test); |
# 非空断言!
| |
| * 非空断言:(变量后加: !) |
| */ |
| let str: string | null = "hello"; |
| console.log(str.length); |
| console.log(str!.length); |
# this 关键字
ts 在函数中使用 this 关键字时,由于其类型要约束,因此需要在函数定义参数处声明其类型,声明方式:使用 this 关键字作为第一个参数,ts 会自动识别这是在为 this 定义类型,后面参数接收和传参时和正常一样。
| |
| * this 关键字 |
| */ |
| function double(this: { value: number }) { |
| |
| this.value = this.value * 2; |
| } |
| const valid = { |
| value: 10, |
| double, |
| }; |
| valid.double(); |
| const valid2 = { |
| val: 1, |
| double, |
| }; |
| |
# declare 关键字
ts 中 declare 关键字就是用于告诉编译器,某个类型存在,并且在当前页面可以使用。
declare 关键字可以描述:
- 变量(const、let、var 命令声明)
- type 或者 interface 命令声明的类型
- class
- enum
- 函数(function)
- 模块(module)
- 命名空间(namespace)
| declare function sayHello(name: string): void; |
| sayHello("张三"); |
# module 关键字
当我们在使用非 TypeScript 编写的库的时候,他们并没有自己的类型,在 ts 项目中引入的时候就会抛出错误,这时候通常在 .d.ts
文件中进行定义。也可以使用 module关键字
来声明某个模块的类型
| declare module "url" { |
| export interface Url { |
| protocol?: string; |
| hostname?: string; |
| pathname?: string; |
| } |
| |
| export function parse( |
| urlStr: string, |
| parseQueryString?, |
| slashesDenoteHost? |
| ): Url; |
| } |
如果想要快速使用,而忽略其类型校验,可以进行简写:
# typeof 操作符
| |
| * typeof 操作符,提取已有变量的类型 |
| */ |
| const center = { |
| x: 0, |
| y: 0, |
| z: 0, |
| }; |
| |
| |
| |
| type position = typeof center; |
| const unit: position = { |
| x: center.x + 1, |
| y: center.y + 1, |
| z: center.z + 1, |
| }; |
# keyof 操作符
| |
| * keyof 操作符,拿到全部的成员变量作为联合类型 |
| */ |
| type Person3 = { |
| name: string; |
| age: number; |
| }; |
| const person7: Person3 = { |
| name: "张三", |
| age: 18, |
| }; |
| function getValueByKey(obj: Person3, key: keyof Person3) { |
| |
| const value = obj[key]; |
| return value; |
| } |
| const age = getValueByKey(person7, "age"); |
| |
# 类型查找
| |
| * 类型查找 |
| */ |
| type res = { |
| user: { |
| name: string; |
| age: number; |
| }; |
| data: { |
| info: string; |
| msg: string; |
| }; |
| }; |
| |
| function getInfo(): res["data"] { |
| return { |
| info: "info", |
| msg: "msg", |
| }; |
| } |
# 类型映射
| |
| * 类型映射 |
| */ |
| type Point2 = { |
| x: number; |
| y: number; |
| z: number; |
| }; |
| |
| export type Readonly<T> = { |
| |
| readonly [item in keyof T]: T[item]; |
| }; |
| const center2: Readonly<Point2> = { |
| x: 1, |
| y: 1, |
| z: 1, |
| }; |
| |
# 类型修饰符
| |
| * 映射修饰符,readonly、? 等 |
| */ |
| type Point3 = { |
| readonly x: number; |
| y?: number; |
| }; |
| type Mapped<T> = { |
| |
| -readonly [item in keyof T]-?: T[item]; |
| }; |
| type Result3 = Mapped<Point3>; |
| |
| export class State<T> { |
| constructor(public current: T) {} |
| update(next: Partial<T>) { |
| this.current = { ...this.current, ...next }; |
| } |
| } |
| export type Partial<T> = { |
| [item in keyof T]?: T[item]; |
| }; |
| const state = new State({ x: 0, y: 0 }); |
| state.update({ y: 124 }); |
| console.log(state.current); |
# interface 和 type 的区别
1、实现对象和类一般使用 interface,
而实现联合类型、元组类型、交叉类型一般使用 type
| |
| |
| type PartialPointX = { x: number }; |
| type PartialPointY = { y: number }; |
| |
| type PartialPoint = PartialPointX | PartialPointY; |
| |
| type Data = [PartialPointX, PartialPointY]; |
| |
| type Name = Number; |
| |
| let div = document.createElement("div"); |
| type B = typeof div; |
2 、 interface 可以多次定义 并被视为合并所有声明成员 type 不支持
| interface Point { |
| x: number; |
| } |
| interface Point { |
| y: number; |
| } |
| const point1: Point = { x: 1, y: 2 }; |
3 、 type 能使用 in 关键字生成映射类型,但 interface 不行。
| type Keys = "firstname" | "surname"; |
| type DudeType = { |
| [key in Keys]: string; |
| }; |
| const test1: DudeType = { |
| firstname: "Pawel", |
| surname: "Grzybek", |
| }; |
4 、 默认导出方式不同
| |
| export default interface Config { |
| name: string; |
| } |
| |
| type Config2 = { name: string }; |
| export default Config2; |
| |
| |
| |
# --------------------------------
# ts 进阶
类型系统
- 对代码中所有的标识符(变量、函数、参数、返回值)进行类型检查
对接 js 引擎优化:ts 虽然不能改变 js 这门动态语言的本质,但是对于 js 代码的类型约束确使得编译出来的 js 代码有利于 v8 等引擎进行优化,例如:
- js 对象由于类型确定,有利于 js 引擎编译时,对象静态编译的优化(v8 引擎会假装 js 对象属性类型确定,根据第一次获取到的形状),这样查找对象的属性更快。
- js 函数由于传入类型确定,有利于字节码编译为机器码时,对执行过程的缓存优化(v8 引擎将字节码编译为机器码后,机器码部分存在一个反编译优化,会将多次执行过程标记为 hot,并进行缓存)
# 配置文件 ts.config.json
# 三方库
- ts-node 将 ts 代码在内存中编译,直接执行
- nodemon 监听文件变化,执行命令,热启动
nodemon --watch src -e ts --exec ts-node test.ts
# 基本类型
- number
- string
- boolean
- 数组 [] 或 Array
- 对象 object 或 Object
- null 和 undefined
- 联合类型:多种类型任选其一,存在类型保护 (例如 typeof 或 if 判断 可以触发类型保护,确定到精确类型)
- void 类型:通常用于约束函数的返回值,表示该函数没有任务返回
- never 类型:通常用于约束函数的返回值,表示该函数永远不可能结束
- 字面量类型:使用一个值进行约束
- 元组类型:一个固定长度的数组,并且数组中每一项的类型确定
- any 类型,绕过类型检查,any 类型的数据可以赋值给任意类型
# 类型别名
对已知的类型进行定义
| type User = { |
| name: string; |
| age: number; |
| }; |
# 函数重载
| function combine(a: number, b: number): number; |
| function combine(a: string, b: string): string; |
| function combine(a: number | string, b: number | string): number | string { |
| if (typeof a === "number" && typeof b === "number") { |
| return a + b; |
| } else if (typeof a === "string" && typeof b === "string") { |
| return a + "、" + b; |
| } |
| throw new Error("a和b必须是相同类型"); |
| } |
# 可选参数
| function sum(a: number, b: number, c?: number): number { |
| if (c) { |
| return a + b + c; |
| } |
| return a + b; |
| } |
| sum(1, 2); |
| sum(2, 3, 5); |
# 扩展类型
# 扩展类型 type
| type User = { |
| name: string; |
| age: number; |
| }; |
| type Condition = (n: number) => boolean; |
| type Condition = { |
| |
| (n: number): boolean; |
| }; |
# 枚举 enum
字面量类型配合联合类型的问题:
- 字面量类型配合联合类型约定了类型的取值,但是如果后面字面量类型名需要修改时,相应的所有的变量赋值也要全部做修改。这时因为联合类型将真实的值和类型混为一体,导致后续修改类型会产生大量的修改。
- 而枚举类型是一种赋值的效果,直接修改 enmu 中的值即可
- 字面量类型不会进去到编译结果
所以需要枚举类型。
| enum ThemeColor { |
| black = "#000000", |
| white = "#ffffff", |
| } |
enum 枚举会参与到编译中,编译的结果就是一个对象,可以在运行时使用 enum 枚举。
注意:
- 枚举的字段(key)必须是字符串或数字
- 数字枚举的值(value)会自动自增,默认从 0 开始,第一项赋值之后从第一项开始
- 被数字枚举约束的变量,可以直接赋值为数字
- 数字枚举的编译结果和字符串有所差异
最佳实践: - 尽量使用统一类型的数据作为枚举项
# 接口 interface
接口 interface 用于约束类、对象和函数的标准
接口常用于对象和类的类型约束,type 常用于变量类型约束。
接口 interface 和类型别名 type,非常相似,但是在类的约束上有所区别,type 常用于多种类型联合结合使用或者定义函数类型常用 type,interface 常用于定义类和对象,提供精细化的接口,并且 interface 可以定义多次,被视为合并,还可以继承
泛型类型约束时,使用该泛型去继承 interface
| interface Person { |
| name: string; |
| age: number; |
| } |
| interface Condition { |
| (n: number): boolean; |
| } |
接口也可以继承,可以通过接口之间的继承,实现多种接口的集合。type 的交叉类型 & 也可以实现类似的效果,但是两者有所差别:
- 子接口不能覆盖父接口的成员
- 交叉类型会把相同成员的类型进行交叉
| interface User { |
| readonly id: "1"; |
| name: "张三"; |
| } |
| let arr: readonly number[] = [1, 2, 3]; |
| |
| arr = [3, 2, 1]; |
泛型中直接有对应的泛型类型,例如 readonly number [] 可以替换为:ReadonlyArray<number>
# 模块解析
模块解析:应该从什么位置寻找模块,在 TS 中,有两种模块解析策略,
- classic:经典
- node:node 解析策略(唯一的变化是,将 js 替换为 ts)
# 类型兼容性
类型不一致时,可以使用类型断言 as
基本类型完全比对
对象类型采用:鸭子比对法:即多类型的可以赋值给部分类型的变量,但是对象字面量赋值时不行(严格遵循类型)。
| interface Duck { |
| name: string; |
| } |
| const b = { |
| name: "咯咯", |
| age: 1, |
| }; |
| const a: Duck = b; |
| |
| |
| |
| |
函数类型约束 参数:(传递给函数的参数可以少,但不许多)。返回值(类型必须匹配)
# 修饰符
# readonly
readonly 表示只读修饰符,不参与最后的编译结果,仅允许对数组或元组字面量使用 readonly 修饰符
# 访问修饰符
访问修饰符用于 class 类中定义属性列表时使用
- public:默认的访问修饰符,公开的,所有代码均可访问
- static:私有的,只有在类中可以访问
- protected:受保护的,不能在外部使用,但是可以在子类中使用
# 类 class
ts 中不允许在 constructor 中动态的添加属性,要求使用 属性列表 来描述类中的属性。
| |
| class user { |
| constructor(name: string) { |
| this.name = name; |
| } |
| } |
| class User { |
| name: string; |
| age: number = 18; |
| sex?: "男" | "女"; |
| readonly id: string; |
| private sno: string; |
| constructor(name: string) { |
| this.name = name; |
| } |
| } |
# 泛型
有时,书写某个函数时,会丢失一些类型信息(多个位置的类型应该保持一致或者有关联的信息),例如参数的类型未知,但是该类型又和返回值的类型相关联,这时就要使用到泛型
泛型是指附属于函数、类、接口、类型别名之上的类型。泛型相当于是一个类型变量,在定义时,无法预先知道具体的类型时,可以用该变量来代替,只有调用时,才会确定他的类型
很多时候,TS 会智能的根据传递的参数,推导出泛型的类型(前提是该参数使用了该泛型),也可以给泛型设置默认值
# 函数中使用泛型
在函数名之后写上泛型: <T>
,使用函数时,在函数名后面添加,例如: <string>
# 类型别名中使用泛型
| type callback<T> = (n: T, i: number) => boolean; |
| function filter<T>(arr: T[], callback: callback<T>): T[] { |
| return arr; |
| } |
# 接口中使用泛型
| interface callback<T> { |
| (n: T, i: number): boolean; |
| } |
# 类中使用泛型
在类中的属性描述中也可以使用泛型。在类中定义,可以约束整个类中所有使用到泛型的地方,和使用该类时传入的类型一致
| class Person<T> { |
| numOrStr: T; |
| constructor(ar: T) { |
| this.numOrStr = ar; |
| } |
| helper(a: T): T[] { |
| return [a]; |
| } |
| } |
| const a = new Person<string>("str"); |
# 泛型继承
使用泛型约束函数时,某些时候泛型的类型未知,但是函数内部又要使用泛型参数对应的方法,这个时候就要对泛型的形状进行声明,可以使用该泛型去继承 interface 接口。
或者当需要对泛型的类型进一步约束时,可以使用泛型去继承 interface 接口。
| interface hasNameProperty { |
| name: string; |
| } |
| function nameToUpperCase<T extends hasNameProperty>(obj: T): T { |
| obj.name; |
| return obj; |
| } |
# 多泛型
| function minxinArray<T, K>(arr1: T[], arr2: K[]): (T | K)[] { |
| if (arr1.length !== arr2.length) throw Error("长度"); |
| let result: (T | K)[] = []; |
| for (let i = 0; i < arr1.length; i++) { |
| result.push(arr1[i]); |
| result.push(arr2[i]); |
| } |
| return result; |
| } |
# --------------------------------------
# 项目中使用 ts
# 注意事项
直接在类型定义文件.d.ts 中书写类型,任意文件夹中直接使用,ts 会自动找对应类型
declare namespace 定义命名空间,这样可以防止使用混乱,还可以防止变量命名冲突
| declare namespace Test { |
| type Person = { |
| name: string; |
| age: number; |
| }; |
| } |
.d.ts 文件可以统一写在 types 文件夹中,也可以写在就近文件夹中
三方库声明支持:当一个三方库中没有定义类型文件时,可以自己手写一个类型声明模块,例如
| declare module "antd-dayjs-webpack-plugin"; |
declare function 定义 TS 环境中已存在的 JS 运行时函数,不能用于定义自己手写的函数,常用于定义约束 es 语法内置的函数类型
| declare function eval(x: string): any; |
declare 声明类型、接口等无效,只能用于声明 function、module 和 namespace
类型定义时,不能写默认参数值
只允许在函数或构造函数实现中使用参数初始化表达式(即参数默认值)
鸭子比对法的含义就是:当一个变量被类型约束之后,可以使用包含全部该类型声明,但类型声明更多的变量或字面量对该变量进行赋值
索引签名允许定义一个对象类型,该类型可以通过索引访问其属性,并且这些属性的值可以是相同的类型。
ts 只支持两种索引签名:字符串和数字,可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。
typeof 操作符用于提取某个变量或类的类型
keyof 操作符用于提取某个类型的字面量类型
| type test = { |
| name: "zhang"; |
| age: 18; |
| }; |
| let a: keyof test; |
| a = "name"; |
| a = "age"; |
| interface NumberDictionary { |
| [index: string]: number; |
| length: number; |
| name: string; |
| } |
索引签名可以结合 TS 高级类型特性使用,例如:映射类型
| |
| type OptionsFlags<T> = { |
| [P in keyof T]: boolean; |
| }; |
元组用于精确数组中每一项的类型,当添加越界的元素时,类型会被限制为元组中每个类型的联合类型
| let arr: [string, number]; |
| arr = ["1", 0]; |
| arr.push(1); |
| arr.push(null); |
lib.es5.d.ts 中自定义的高级泛型
| |
| interface User { |
| name: string; |
| age: number; |
| } |
| |
| type UserPartial = Partial<User>; |
| |
| interface User1 { |
| id?: string; |
| sex?: number; |
| } |
| |
| type UserRequired = Required<User1>; |
| |
| type User2 = { |
| name: string; |
| id: number; |
| }; |
| |
| interface UserReadOnly extends Readonly<User2> {} |
- Pick 泛型:从类型 T 中选取部分值的类型,创建一个新的类型。(泛型使用时需要传入类型和选取的类型属性)
| |
| type User3 = { |
| name: string; |
| age: number; |
| id: number; |
| }; |
| |
| type UserPicked = Pick<User3, "name" | "age">; |
- Record 泛型:构造一个类型,其键为第一个泛型参数类型,值为第二个泛型参数类型
| |
| type User4 = { |
| age: number; |
| id: number; |
| }; |
| |
| type UserRecored = Record<string, number>; |
- Exclude 泛型:排除类型中的某一个类型选项!(适用于对象类型)
| |
| type Unicode = "foo" | "bar" | "baz"; |
| type userExclude = Exclude<Unicode, "foo">; |
- Extract 泛型:指定类型中的某一个类型选项!
| |
| type Unicode1 = "foo" | "bar" | "baz"; |
| type UserExtract = Extract<Unicode1, "baz">; |
- Omit 泛型:创建一个新的类型,排除类型中指定类型(第二个参数类型)(适用于联合类型)
| |
| type User5 = { |
| name: string; |
| age: 13; |
| }; |
| |
| type UserOmit = Omit<User5, "age">; |
- type NonNullable<T> = T & {}; 内置的排除 undefined 和 null 的泛型(巧妙使用了条件类型去精确类型)。
- & 在交叉类型中,具有多种类型全部的属性和方法,这里传入空对象不会对原来造成影响,但是使用了 & 条件类型,就会通过类型排除和类型首位精确定义类型定义。
- Parameters 泛型,用于提取出函数参数的类型,并将其封装为元组
- ConstructorParameters 泛型,用于提取出构造函数的参数类型(传入 typeof dmq 类),并将其封装为元组
- ReturnType 泛型,用于提取函数返回值的类型
- Awaited 泛型:尝试推断一个具有 then 方法(Promise)的最终完成值的类型
| function fetchUser(): Promise<{ id: number; name: string }> { |
| return new Promise((resolve) => { |
| resolve({ id: 1, name: "张三" }); |
| }); |
| } |
| type User = Awaited<ReturnType<typeof fetchUser>>; |
- instanceType 泛型:获取一个构造函数类型的实例类型
| type InstanceTyped = InstanceType<typeof MyClass>; |
- Uppercase Lowercase 泛型,将一个字符串类型的所有字符转为大(小)写
| type UpperHello = Uppercase<"hello">; |
- Capitalize 泛型,将字符串类型的首字符转为大写
# react 项目中使用 TS
配置 ts.config.json
- lib 选项表示编译过程中需要引入的库文件列表,react 项目中必须在 lib 中包含 dom 选项
- jsx 选项表示编译过程中如何处理 jsx 语法,react 项目中配置为 preserve,保留给 babel 处理即可,也可以配置为 react-jsx
其余部分详见官网