TS 相关
TypeScript
TypeScript 相较于 JavaScript 有什么优势和劣势?
TypeScript 泛型
TS 类型有哪些
TS 类型包含所有 JS 类型 null, undefined, string, number, boolean, bigInt, Symbol, object(数组, 对象, 函数, 日期), 还包含 void, never, enum, unknown, any 以及 自定义的 type 和 interface
各类型变量声明
var/let/const 标识符: 数据类型 = 赋值;
-
基础类型
const name: string = "xiaoming"; const age: number = 18; const isABoy: boolean = true; const hobby: null = null; const specialty: undefined = undefined; // Symbol const speedSymbol = Symbol("speedSymbol"); const info = { [speedSymbol]: "7s", };
-
数组和元组[^1]
// 数组 // 写法 1 const arr1: Array<number> = []; // 写法 2 const arr2: number[] = []; // 数组里包含多个类型 const arr3: (number | string)[] = [1, "hello"]; // 元组 // 给定的类型与数组元素按照相应的索引保持一致 const tuple1: [string, number] = ["xiaoming", 18]; // 如下错误写法: const tupleErr: [string, number] = [18, "xiaoming"];
-
对象类型
// TS 中的 object 类型泛指所有的的非原始类型, 如对象, 数组, 函数. // 使用 object 声明的对象无法向对象中添加属性, 也无法被修改: const obj1: object = { name: "xiaoming", }; obj1.age = 18; // TS 错误 obj1.name = "xiaohong"; // TS 错误 // 应当使用更准确的类型限制: const obj2: { name: string, age?: number } = { name: "xiaoming", }; // ? 表示可选参数
-
其他类型
-
any: 无法确定一个变量的类型, 可使用 any, 对其做任何操作都是合法的, 即使访问了一个不存在的属性; 场景: 1. 类型过于繁琐; 2. 引入一些第三方库时缺失类型注解 3. 兼容老代码;
-
unknown: 表示一个值可以是任何类型, 它是所有类型的父类型, 任何类型都可以赋值给 unknown 类型, 但 unknown 类型只能赋值给 any 类型和 unknown 类型本身
-
never: 假如一个函数的返回结果是死循环或者异常, 我们可以使用 never 类型表示这种永不存在值的类型
-
unknown 和 any 的区别: unknown 类型的变量不能直接赋值给其他类型的变量, 也不能调用其上的任何方法或属性, 除非先做类型判断或者进行类型断言
// unknown 类型的变量不能直接赋值给其他类型的变量, 也不能调用其上的任何方法或属性, 除非先做类型判断或者进行类型断言 const fun1 = (res: unknown) => { // 类型判断后使用 if (type of res === "number") { console.log(res.toFixed(2)) } else if (type of res === "string") { console.log(res.toUpperCase()) } }; const func2 = (res: unknown) => { // 类型断言后使用 const num = res as number; console.log(num.toFixed(2)); }
-
-
函数类型
// 声明函数时, 可以在每个参数后添加类型注解, 声明其参数类型. 也可以声明返回值的类型, 如果没有声明返回值类型, TS 会自动推导 // 普通函数只声明参数类型 function func1(n1: number) { return n1; } // 普通函数声明参数类型和返回值类型 function func1(n1: number): number { return n1; } // 箭头函数只声明参数类型 const func2 = (n1: number) => { return n1; }; // 箭头函数声明参数类型和返回值类型 `const func2 = (n1: number) => number = n1 => { return n1; };`;
-
枚举类型 枚举类型将一组可能出现的值, 一个个列举, 定义在一个类型中, 这个类型就是枚举
// 使用和对象一致, 如果不指定值, 则从 0 累加 enum Direction { LEFT = "LEFT", RIGHT = "RIGHT", OTHER = "OTHER", } const getDirection = (direction: Direction) { switch (direction) { case Direction.LEFT: console.log("LEFT"); break; case Direction.RIGHT: console.log("RIGHT"); break; case Direction.OTHER: console.log("OTHER"); break; default: const foo: never = direction; break; } }
[^1]:
元组(Tuple): 元组是指已知数组元素数量和类型的数组. 成员的类型不必保持一致, 但成员类型需要与相应索引的类型保持一致, 顺序对应. 如:
const [state, setState] = useState([])
interface 和 type
都可以约束对象的结构, 使用 interface 定义接口, 使用 type 定义类型别名
基本用法
// 方式 1, interface
interface IPoint {
x: number,
y?: string, // ? 代表非必选
readonly z: string, // readonly 代表只读
}
// 方式 2, type
type Point {
x: number,
y: string,
}
interface 和 type 区别:
- interface 只描述对象, type 则可以描述所有数据
- interface 使用 extends 来实现继承, type 使用 & 来实现交叉类型
- interface 会创建新的类型名, type 只是创建类型别名, 并没有创建新类型
- interface 可以重复声明扩展, type 则不行(别名是不能重复的)
索引签名(Index Signatures)
// 表示对象中满足属性键为 number, 值为 string, k 为形参,可随意替换
interface A1 {
// 动态成员
[k: number]: string;
}
// 等价于
type A2 = Record<number, string>;
const obj: A1 = {
0: "js",
1: "ts",
2: "索引签名",
};
interface 接口继承
接口和类继承相同, 都是使用 extends 关键字 接口支持多继承, 类不支持
interface Animal {
running: () => void; // voide 声明一个没有返回值的函数
}
interface Person {
name: string;
age: number;
}
// 自动扩展 Person 类型
interface Person {
sex: string;
}
// 手动使用 extends 继承
interface Students extends Person, Animal {
id: number;
}
const student1: Students = {
name: "xiaoming",
age: 18,
sex: "male",
id: 1,
running() {},
};
interface 接口实现
定义的接口可以被类实现, 之后需要传入接口的地方同样可以将类实例传入, 这就是面向接口开发
interface IRun {
running: () => void
}
interface IEating{
eating: () => void
}
class Person implements IRun, IEating {
running(){},
eating(){}
}
function run(runner: IRun) {
runner.running()
}
const p = new Person();
run(p);
函数
基本使用
我们可以编写函数类型的表达式(Function Type Expressions),来表示函数类型
const nameArr = ["zhangsan", "lisi", "wangwu"];
// 匿名函数参数会根据上下文自动推导类型
nameArr.forEach((item) => {
console.log(item);
});
type F1 = (a: number, b: number) => number;
// 箭头函数
const f1: F1 = (a, b) => a + b;
// 函数表达式
const f2: F1 = function (a, b) {
return a + b;
};
// 同样也可以先写函数及其类型, 再使用 typeof 获取其函数的类型
const f3 = (a: number, b: number): number => a + b;
type F2 = typeof f3; // 返回值类型
调用签名(Call Signatures)
函数除了被调用,也可以有自己的属性值
interface FunctionWithAttrs {
(msg: string): void;
attr: string;
}
// fn 要实现一个函数有 attr 属性的要求
const fn: FunctionWithAttrs = (mgs) => {
console.log("🧊 - mgs:", mgs);
};
fn.attr = "attributes";
构造签名(Construct Signatures)
- 函数也可以使用 new 操作符去当作构造函数
- 使用构造签名,即在调用签名前面加 new 关键词
interface ClassWithConstructor {
new(ctor: string): void;
}
// outerClass 可使用 new
function factory(outerClass: ClassWithConstructor) {
const instance = new outerClass("hello");
}
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
factory(Person); // ok
this
TS 中默认情况下如果没有指定 this 的类型,this 是 any 类型 我们可以在函数第一个参数声明 this 的类型,函数调用传入的参数从第二个开始接收
// this 名字不可变, 会在编译后被抹除
function fn(this: { name: string }, n: number) {
console.log(this.name); // ok
console.log(n); // 10
}
fn.call({ name: "zhangsan" }, 10);
// ThisParameterType 提取函数类型的 this 参数类型,如果没有 this 参数则返回 unknown
function fn2(this: { name: string }, n: string): number {
return 10;
}
type thisType = ThisParameterType<typeof fn2>; // 获取函数 this 类型
// OmitThisParameter 移除函数类型 this 参数类型,返回当前函数类型
type FnType = OmitThisParameter<typeof fn>; // 移除函数 this 类型, 返回当前函数类型
// ThisType 指定所在对象的所有方法里面 this 的类型
// TODO
联合类型(Union Type)、交叉类型、函数重载
联合类型和函数重载
- TS 允许我们使用多种运算符,从现有类型中构建新类型
- 联合类型就是一种组合类型的方式,多种类型满足一个即可,使用 | 符号,其中每个联合的类型被称之为联合成员(union's members)
- 函数重载则是我们可以去编写不同的**重载签名(overload signatures)**表示函数可以不同的方式调用,一般写两个及以上的重载签名,再编写一个通用函数的实现
// 现在有个函数, 可以传入字符串或数组以获取长度
// 1. 使用联合类型
function getLength(args: string | any[]): number {
return args.length;
}
getLength("hello"); // 返回 5
getLength([1, 2]); // 返回 2
// 2. 使用函数重载
function getLength(args: string): number;
function getLength(args: any[]): number;
function getLength(args: any): number {
return args.length;
}
getLength("hello"); // 返回 5
getLength([1, 2]); // 返回 2
交叉类型
- 交叉类型则是满足多个类型的条件,使用 & 符号
- 例如 type MyType = number & string,满足一个既要是 number 类型,也要是 string 类型的值,显然没有值满足,则会交叉成 never 类型
- 进行交叉时,通常是使用对象类型交叉
interface Colorful {
color: string;
}
interface IFly {
flying: () => void;
}
type newType = Colorful & IFly; // 交叉类型
类型、非空、常量断言
- 类型断言 as,当 TS 无法获取到具体的类型信息,就需要使用类型断言(Type Assertions)
- 它可以允许我们断言成更具体或者不太具体的(比如 any)的类型
const arr = [1, 2, 3, 5];
const res = arr.find(i => i>0);
const num1 = res as number; // 断言成 number 类型 // 或使用 <number>xxx (JSX 内不能使用)
const str1 = res as string; // 断言成 string 类型
const any1 = res as any; // 断言成 any 类型
// 非空类型断言
function fn (msg?: string) {
console.log(msg!.length); // 非空类型断言 !,当我们确定参数有值,需要跳过 TS 对它的检测的时候可以使用
}
// 常量断言 as const,将类型尽量收窄到字面量类型,如果用在对象后面,相当于给对象里面每个成员加上 readonly并收窄
const arr = [1, "hello"] as const;
const obj = {
name: "hello",
age: 20,
} as const;
字面量类型
- 字面量类型是 TS 的基本类型之一,它可以表示一个值,比如数字、字符串、布尔值、null、undefined、symbol、数组、元组、对象、函数等。
- 字面量类型与其他类型不同的是,字面量类型不允许使用变量来定义,只能直接写出字面量的值。
let msg: "hello" = "hello";
type Alignment = "left" | "right" | "center";
let align: Alignment = "center";
align = "right";
类型收窄(Type Narrowing): 通过**类型保护(type guards)**来收窄类型
- 类型保护是一种特殊的函数,它会返回一个布尔值,如果返回 true,则表示当前条件成立,否则返回 false。
- 当我们使用类型保护时,TS 会自动推断出类型,但是当我们需要指定类型的时候,可以使用 as 关键字来指定类型。
常见的类型保护有
- typeof
- Switch 或者一些相等运算符(=== 、 !==)来表达相等性
- instanceof
- in
// typeof 收窄
function unionType(id: string | number) {
if (typeof id === "string") {
id.toUpperCase();
} else {
id.toString();
}
}
// 真值收窄
function getMsg(msg?: string) {
if (msg) {
msg.toUpperCase();
}
}
// 相等收窄 - TODO
// in 收窄
// instanceof 收窄
泛型(Generics)
泛型是指在定义函数时,不指定具体的类型,而是在使用的时候,指定具体的类型。
- 可以解决输入输出一致的问题
- 适合不同场景的复用
- 定义函数的时候不决定参数的类型
- 而是让调用者使用尖括号形式传入对应函数
// 比如实现一个函数, 传入一个参数并返回他, 保证这个参数和返回值类型一致
function getMsg<Type>(msg: Type): Type {
return msg;
}
// 通过<类型>的方式将类型传递给函数
const num = getMsg<number>(123);
// 或者不指定类型, 通过类型推导来确定传参类型
const str = getMsg("hello");
// TODO
映射类型(Mapped Types)
- 映射类型是 TS 中的一种高级类型,它可以用来从一个现有类型中生成一个新的类型
- TS 大部分的内置工具和类型体操都是基于映射类型实现
- 映射类型的语法形式是 { [K in keyof T]: U }
- 其中 K 是 T 的所有属性名的联合类型,keyof 是一个索引类型查询操作符,用来获取一个类型的所有属性名的联合类型。
- U 是一个类型变换函数,它用来将 T 中的每个属性类型变成另一个类型
interface IPerson {
name: string;
age: number;
}
type MySelfType<T> = {
[K in keyof T]: T[K];
};
type MapType<T> = {
[P in keyof T]: (arg: T[P]) => boolean;
};
// 类型不变 相当于复制一份
type R1 = MySelfType<IPerson>; // IPerson
type R2 = MapType<IPerson>; // { name: string; age: number }
// 修饰符 readonly 和 ?
interface IPerson2 {
readonly name: string;
age?: number;
}
type MapType2<T> = {
-readonly [P in keyof T]-?: T[P];
};
type NewType = MapType2<IPerson2>; // { readonly name: string; age?: number }