额外补充
TypeScript 基础类型
Boolean\Number\String\Array\Typle
Tuple(元组) 类型
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
// Declare a tuple type
let x: [string, number]
// Initialize it
x = ['hello', 10] // OK
// Initialize it incorrectly
x = [10, 'hello'] // Error
当访问一个已知索引的元素,会得到正确的类型:
console.log(x[0].substr(1)) // OK
console.log(x[1].substr(1)) // Error, 'number' does not have 'substr'
当访问一个越界的元素,会使用联合类型替代:
x[3] = 'world' // OK, 字符串可以赋值给(string | number)类型
console.log(x[5].toString()) // OK, 'string' 和 'number' 都有 toString
x[6] = true // Error, 布尔不是(string | number)类型
react useState 返回的就是一个元组类型
Unknown 类型
let value: unknown
value = true // OK
value = 42 // OK
value = 'Hello World' // OK
value = [] // OK
value = {} // OK
value = Math.random // OK
value = null // OK
value = undefined // OK
value = new TypeError() // OK
value = Symbol('type') // OK
可进行随意赋值,不可随便访问
let value: unknown
value.foo.bar // Error
value.trim() // Error
value() // Error
new value() // Error
value[0][1] // Error
访问前作类型判断
let fun: unknown
fun = 123
if (typeof fun === 'function') {
fun()
}
unknown 类型只能被赋值给 any 类型和 unknown 类型本身
let value: unknown
value.foo.bar // Error
value.trim() // Error
value() // Error
new value() // Error
value[0][1] // Error
Void 类型
void 类型像是与 any 类型相反,它表示没有任何类型
// 声明函数返回值为void
function warnUser(): void {
console.log("This is my warning message");
}
Null 和 Undefined
TypeScript 里,undefined 和 null 两者各自有自己的类型分别叫做 undefined 和 null
let u: undefined = undefined
let n: null = null
如果关闭了 --strictNullChecks 标记,undefined 和 null 可以赋值给别的类型
let a = 1
a = undefined
我们默认都是开启 strictNullChecks,如果想将 undefined 和 null 赋值给别的类型,使用联合类型
let a: number | null | undefined = 1
a = undefined
Never 类型
never 类型表示的是那些永不存在的值的类型
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message)
}
function infiniteLoop(): never {
while (true) {}
}
可以利用 never 类型的特性来实现全面性检查
type Foo = string | number
function controlFlowAnalysisWithNever(foo: Foo) {
if (typeof foo === 'string') {
// 这里 foo 被收窄为 string 类型
} else if (typeof foo === 'number') {
// 这里 foo 被收窄为 number 类型
} else {
// 已经穷尽判断存在的类型,foo 在这里是 never
const check: never = foo
}
}
如果有一天改了 Foo 类型
type Foo = string | number | boolean
而忘记同时修改 controlFlowAnalysisWithNever 方法中的控制流程,这时候 else 分支的 foo 类型会被收窄为 boolean 类型,导致无法赋值给 never 类型,这时就会产生一个编译错误
Enum 类型
enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}
let dir: Direction = Direction.NORTH;
console.log(dir) // 0 NORTH初始为0,其余的成员会从 1开始自动增长
enum Direction {
NORTH = 1,
SOUTH,
EAST,
WEST,
}
let dir: Direction = Direction.NORTH;
console.log(dir) // 1 NORTH初始为1,其余的成员会从2开始自动增长
// 字符串枚举
enum Direction {
NORTH = "NORTH",
SOUTH = "SOUTH",
EAST = "EAST",
WEST = "WEST",
}
console.log(Direction.NORTH)// NORTH
枚举是在运行时真正存在的对象
反向映射
enum Enum {
A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"
以上代码对于的 ES5 代码如下
var Enum
;(function (Enum) {
Enum[(Enum['A'] = 0)] = 'A'
})(Enum || (Enum = {}))
var a = Enum.A
var nameOfA = Enum[a] // "A"
可以解读为
var Enum = {}
Enum['A'] = 0
Enum[0] = 'A'
var a = Enum.A // 0
var nameOfA = Enum[a] // "A"
TypeScript 断言
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
联合类型和类型别名
// 联合类型
let a: number | string | undefined
// 类型别名
type A = number | string | undefined
let a: A
type User = {
name: string,
age: number,
}
交叉类型
交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所有类型的特性。
type Man = {
name: string,
age: number,
gender: '男' | '女',
}
type Women = {
name: string,
age: number,
}
type User = Man & Women
const user: User = {
name: '1212',
age: 18,
gender: '女',
}
TypeScript 函数
函数类型
function add(x: number, y: number): number {
return x + y
}
const add1 = function (x: number, y: number): number {
return x + y
}
const add2 = (x: number, y: number): number => {
return x + y
}
完整函数类型
const myAdd: (baseValue: number, increment: number) => number = (x: number, y: number): number => {
return x + y
}
或者
type SomeFunType = (baseValue: number, increment: number) => number
// TS 默认推导出 x y 的类型
const myAdd: SomeFunType = (x, y): number => {
return x + y
}
可选参数及默认参数
// 可选参数
function createUserId(name: string, id: number, age?: number): string {
return name + id
}
// 默认参数
function createUserId(name: string = 'Semlinker', id: number, age?: number): string {
return name + id
}
在声明函数时,可以通过 ? 号来定义可选参数,比如 age?: number 这种形式。在实际使用时,需要注意的是可选参数要放在普通参数的后面,不然会导致编译错误。
使用了 --strictNullChecks,可选参数会被自动地加上 | undefined:
剩余参数
function buildName(firstName: string, ...restOfName: string[]) {
return `${firstName} ${restOfName.join(' ')}`
}
const employeeName = buildName('Joseph', 'Samuel', 'Lucas', 'MacKinzie')
TypeScript 数组
数组解构
const five_array = [0, 1, 2, 3, 4]
const [x, y, z] = five_array
数组展开运算符
let two_array = [0, 1]
let five_array = [...two_array, 2, 3, 4]
TypeScript 对象
对象解构
let person = {
name: 'Semlinker',
gender: 'Male',
}
let { name, gender } = person
对象展开运算符
const person = {
name: 'Semlinker',
gender: 'Male',
address: 'Xiamen',
}
// 组装对象
const personWithAge = { ...person, age: 33 }
// 获取除了某些项外的其它项
const { name, ...rest } = person
const { gender, address } = rest
TypeScript 接口
在面向对象语言中,接口是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类去实现。 TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
对象的形状
interface Person {
name: string;
age: number;
}
let Semlinker: Person = {
name: 'Semlinker',
age: 33,
}
可选 | 只读属性
interface Person {
readonly name: string;
age?: number;
}
函数类型
interface SearchFunc {
(source: string, subString: string): boolean;
}
可索引的类型
与使用接口描述函数类型差不多,我们也可以描述那些能够“通过索引得到”的类型,比如 a[10]或 ageMap["daniel"]。 可索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。
interface StringArray {
[index: number]: string;
}
let myArray: StringArray
myArray = ['Bob', 'Fred']
let myStr: string = myArray[0]
接口 vs 类型别名
type Alias = { num: number }
interface Interface {
num: number;
}
declare function aliased(arg: Alias): Alias
declare function interfaced(arg: Interface): Interface
在编译器中将鼠标悬停在 interfaced 上,显示它返回的是 Interface,但悬停在 aliased 上时,显示的却是对象字面量类型。
类型别名不能被 extends 和 implements(自己也不能 extends 和 implements 其它类型)
接口声明时不能使用联合类型或交叉类型
类型守卫
联合类型中我们只能访问共同拥有的成员。
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
function getSmallPet(): Fish | Bird {
// ...
}
let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim(); // errors
let pet = getSmallPet();
// JavaScript里常用来区分2个可能值的方法是检查成员是否存在,这里每一个成员访问都会报错
if (pet.swim) {
pet.swim();
}
else if (pet.fly) {
pet.fly();
}
let pet = getSmallPet();
// 使用断言
if ((<Fish>pet).swim) {
(<Fish>pet).swim();
}
else {
(<Bird>pet).fly();
}
自定义类型保护的类型谓词
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}
pet is Fish 就是类型谓词。 谓词为 parameterName is Type 这种形式, parameterName 必须是来自于当前函数签名里的一个参数名。
// 'swim' 和 'fly' 调用都没有问题了
if (isFish(pet)) {
pet.swim()
} else {
pet.fly()
}
TypeScript 不仅知道在 if 分支里 pet 是 Fish 类型; 它还清楚在 else 分支里,一定 不是 Fish 类型,一定是 Bird 类型。
in 关键字
interface Admin {
name: string;
privileges: string[];
}
interface Employee {
name: string;
startDate: Date;
}
type UnknownEmployee = Employee | Admin
function printEmployeeInformation(emp: UnknownEmployee) {
console.log('Name: ' + emp.name)
if ('privileges' in emp) {
console.log('Privileges: ' + emp.privileges)
}
if ('startDate' in emp) {
console.log('Start Date: ' + emp.startDate)
}
}
typeof 关键字
function padLeft(value: string, padding: string | number) {
if (typeof padding === 'number') {
return Array(padding + 1).join(' ') + value
}
if (typeof padding === 'string') {
return padding + value
}
throw new Error(`Expected string or number, got '${padding}'.`)
}
typeof 类型保护只支持两种形式:typeof v === "typename" 和 typeof v !== typename,"typename" 必须是 "number", "string", "boolean" 或 "symbol"。 但是 TypeScript 并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。
instanceof 关键字
interface Padder {
getPaddingString(): string;
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) {}
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}
class StringPadder implements Padder {
constructor(private value: string) {}
getPaddingString() {
return this.value;
}
}
let padder: Padder = new SpaceRepeatingPadder(6);
if (padder instanceof SpaceRepeatingPadder) {
// padder的类型收窄为 'SpaceRepeatingPadder'
}
TypeScript 泛型
当我们需要使用一个函数, 当它传入数字的时候, 需要返回这个数字
function id(value: number): number {
return value
}
但是这个函数的需求是, 需要在传入 string 的时候, 也能够返回 string, 一般情况下, 我们可以这样写
function id(value: number | string): number | string {
return value
}
这种方法也可以, 但是无法完全限定死它的类型, 因为我们的需求是传入的类型等于传出的类型, 在这个类型定义中, 我们可以传入 number, 传出 string, 也是能够通过类型检测的 如果传入的 value 有很多种类型怎么办呢? 这个时候就需要使用到泛型了
function id<T>(value: T): T {
return value
}
// 常规调用
id < number > 1
// 简略调用(自动类型判断)
id(1)
使用典型场景: 当我们需要一个函数, 函数传入的值与传出值存在某种特定数据类型联系的时候, 需要使用到泛型
除了为类型变量显式设定值之外,一种更常见的做法是使编译器自动选择这些类型,从而使代码更简洁。我们可以完全省略尖括号,比如:
function identity<T, U>(value: T, message: U): T {
console.log(message)
return value
}
console.log(identity(68, 'Semlinker'))
上面的例子,如果想要返回两种类型,我们可以这样
// 使用元组
function identity<T, U>(value: T, message: U): [T, U] {
return [value, message]
}
除此之外,还可以使用泛型接口
泛型接口
interface Identities<V, M> {
value: V;
message: M;
}
function identity<T, U>(value: T, message: U): Identities<T, U> {
console.log(value + ': ' + typeof value)
console.log(message + ': ' + typeof message)
let identities: Identities<T, U> = {
value,
message,
}
return identities
}
console.log(identity(68, 'Semlinker'))
泛型约束
function identity<T>(arg: T): T {
console.log(arg.length) // Error
return arg
}
在这种情况下,编译器将不会知道 T 确实含有 length 属性,尤其是在可以将任何类型赋给类型变量 T 的情况下。我们需要做的就是让类型变量 extends 一个含有我们所需属性的接口,比如这样:
interface Length {
length: number;
}
function identity<T extends Length>(arg: T): T {
console.log(arg.length); // 可以获取length属性
return arg;
}
T extends Length 用于告诉编译器,我们已经实现 Length 接口的任何类型。之后,当我们使用不含有 length 属性的对象作为参数调用 identity 函数时,TypeScript 会提示相关的错误信息:
identity(68) // Error
// Argument of type '68' is not assignable to parameter of type 'Length'.(2345)
泛型参数默认类型
interface A<T=string> {
name: T;
}
const strA: A = { name: "Semlinker" };
const numB: A = { name: 101 };// error,不能将类型“number”分配给类型“string”。ts(2322)
const numC<number>: A = { name: 101 };
泛型条件类型
条件类型会以一个条件表达式进行类型关系检测,从而在两种类型中选择其一:
T extends U ? X : Y
若 T 能够赋值给 U,那么类型是 X,否则为 Y
type K = 1 | 2 | 3 | 4;
type A<T> = {
key1: T;
key2: T extends K ? number : string;
};
const a1: A<5> = {
key1: 5,
key2: '123',
};
const a2: A<4> = {
key1: 4,
key2: 123,
};
特殊关键字
keyof
获取接口或类型的键名,返回的是联合类型
interface IUser {
name: string;
age: number;
}
type TUserKey = keyof IUser; // type TUserKey = "name" | "age"
// 一般来讲, 不建议对一些普通的 string 类型等使用 keyof, 因为 js 本身的对象 key, 可能会产生非预期的结果
type T2 = keyof keyof TUserKey; // type T2 = "toString" | "valueOf"
typeof
获取常量的类型
const a = { a: 1, b: 2, c: '3' }
type T0 = typeof a // type T0 = {a: number; b: number; c: string}
in
遍历联合类型
const a = { a: 1, b: 2, c: '3' };
type T2 = keyof typeof a; // type T2 = "a" | "b" | "c"
type T3 = { [p in T2]: number }; // type T3 = { a: number; b: number; c: number; }
infer
表示带推断的一个类型变量
假设我们有一个这样的需求, 需要获取到函数参数的类型, 并把这个类型赋值给另一个类型, 这个时候我们可以这样写
type TParamType<T> = T extends (param: infer P) => any ? P : T;
interface IUser {
name: string;
age: number;
}
type TFunc = (user: IUser) => void;
type TParam = TParamType<TFunc>; // type TParam = User;
type TStr = TParamType<string>; // TStr = string;
内置的类型别名
Partial
改所有键值为可选
// ts 中的实现方式
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 使用方式
interface IUser {
name: string;
age?: number;
}
type TPartialUser = Partial<IUser>;
// 输出的 TPartialUser 结果
// type TPartialUser = {
// name?: string | undefined;
// age?: number | undefined;
// }
Required
改所有键值为必选
// ts 中的实现方式
type Required<T> = {
[P in keyof T]-?: T[P];
};
// 使用方式
interface IUser {
name: string;
age?: number;
}
// 改 IUser 的接口值为均必选
type TRequiredUser = Required<IUser>;
// 输出的 TRequiredUser 结果
// type TRequiredUser = {
// name: string;
// age: number;
// }
Readonly
将传入属性变为只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
给子属性添加 readonly 的标识,如果将上面的 readonly 改成 -readonly, 就是移除子属性的 readonly 标识。
Pick
将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型。
// node_modules/typescript/lib/lib.es5.d.ts
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, 'title' | 'completed'>;
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
};
Record
Record<K extends keyof any, T> 的作用是将 K 中所有的属性的值转化为 T 类型。
type Record<K extends keyof any, T> = {
[P in K]: T;
};
interface PageInfo {
title: string;
}
type Page = 'home' | 'about' | 'contact';
const x: Record<Page, PageInfo> = {
about: { title: 'about' },
contact: { title: 'contact' },
home: { title: 'home' },
};
Exclude
将某个类型中属于另一个的类型移除掉
type Exclude<T, U> = T extends U ? never : T;
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
Extract
与 Exclude 恰好相反, 代表取出对应值
type Extract<T, U> = T extends U ? T : never;
type T0 = Extract<'a' | 'b' | 'c', 'a'>; // "a"
ReturnType
ReturnType 的作用是用于获取函数 T 的返回类型。
type ReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => infer R
? R
: any;
ThisType
指定上下文的类型
ThisType 在 TS 的类型声明层面其实就是一个空的对象类型, 但是通过源码层面对这个内置类型别名进行了支持
interface Person {
name: string;
age: number;
}
const obj: ThisType<Person> = {
dosth() {
this.name // string
}
}
NonNullable
过滤类型中的 null 和 undefined
type NonNullable<T> = T extends null | undefined ? never : T;
Parameters
获取函数的传入值的所有参数
type Parameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? P : never;
常用的自定义类型别名
Omit
移除掉类型中的某个值
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
Deferred
返回内容值为 Promise
type Deferred<T> = {
[P in keyof T]: Promise<T[P]>;
};
AsyncReturnType
解开 Promise
type AsyncReturnType<T> = T extends Promise<infer U>
? U
: T extends (...args: any) => Promise<infer U>
? U
: T extends (...args: any) => infer U
? U
: T;
const fetchDepRolesWithTag = (deptCode: string, servicelineCode: string) => {
return (
request <
IRequestResult<{
records: {
roleCode: string;
roleName: string;
tag: {
code: string;
name: string;
}[];
}[];
}>(`/api/bms/department/role/tag`, {
method: 'get',
params: {
deptCode,
servicelineCode,
current: 1,
size: -1,
},
})
);
};
type FetchDepRolesWithTagType = typeof fetchDepRolesWithTag;
// type FetchDepRolesWithTagType = (deptCode: string, servicelineCode: string) => Promise<IRequestResult<{
// records: {
// roleCode: string;
// roleName: string;
// tag: {
// code: string;
// name: string;
// }[];
// }[];
// }>>
type FetchDepRolesWithTagReturnType = AsyncReturnType<FetchDepRolesWithTagType>;
// type FetchDepRolesWithTagReturnType = IRequestResult<{
// records: {
// roleCode: string;
// roleName: string;
// tag: {
// code: string;
// name: string;
// }[];
// }[];
// }>
type GenericType<T> = T extends IRequestResult<infer U> ? U : T;
type FetchDepRolesWithTagReturnResultType = GenericType<FetchDepRolesWithTagReturnType> ;
// type FetchDepRolesWithTagReturnResultType = {
// records: {
// roleCode: string;
// roleName: string;
// tag: {
// code: string;
// name: string;
// }[];
// }[];
// };
// 上面转换合并为
type RequestResultType<T> = GenericType<AsyncReturnType<T>>;
type ResultType = RequestResultType<typeof fetchDepRolesWithTag>;
PartialOptional
type PartialOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
PartialOmit
type PartialOmit<T, K extends keyof T> = Pick<T, K> & Partial<Omit<T, K>>;
补充
显式赋值断言
let x!: number[];
initialize();
x.push(4);
function initialize() {
x = [0, 1, 2, 3];
}
非空断言
ttype A = {
key1?: {
key1_1: number;
};
};
const a: A = {
key1: {
key1_1: 1,
},
};
console.log(a.key1!.key1_1);
可选链
type A = {
key1?: {
key1_1: number;
};
};
const a: A = {
key1: {
key1_1: 1,
},
};
console.log(a.key1?.key1_1);
// console.log((_a = a.key1) === null || _a === void 0 ? void 0 : _a.key1_1);
空值合并运算
let x = foo ?? bar();
// let x = foo !== null && foo !== void 0 ? foo : bar();