跳到主要内容

入门

栗子

export type SetState<S extends Record<string, any>> = <K extends keyof S>(
state: Pick<S, K> | null | ((prevState: Readonly<S>) => Pick<S, K> | S | null),
) => void;

解析:

  • 这段代码定义了一个名为SetState的类型:去掉一些类型定义之后,就是export type SetState = (state) => void

    • SetState是个函数签名,返回为空(void)
  • <S extends Record<string, any>> 是个类型参数

    • Record<string, any> 是个泛型类型,必须是个对象,其中的键是字符串,而值可以是任意类型。
    • extends:继承,意味着S也必须是个对象,其中的键是字符串,而值可以是任意类型
  • <K extends keyof S>也是个类型参数

    • keyof 是一个关键字,用于获取一个类型的所有属性名(键)组成的联合类型
    • 这段代码意味着K必须是S的属性名组成的联合类型
  • state: Pick<S, K> | null | ((prevState: Readonly<S>) => Pick<S, K> | S | null): 定义参数state的类型

    • Pick<S, K>:表示部分状态更新,仅更新对象 S 中指定的键 KPick 是一个内置类型,用于从一个类型中选取部分属性来创建一个新的类型。Pick 类型允许您从原始类型中选择一些属性,而忽略其他属性。
    • null:表示将状态重置为 null。
    • (prevState: Readonly<S>) => Pick<S, K> | S | null:这是一个函数
      • 接受prevState作为参数, Readonly 是一个内置类型 ,用于创建一个只读版本的指定类型
      • 返回部分状态更新( Pick<S, K>)、整个状态 S,或者 null

泛型

泛型(Generics)是编程语言中的一个 概念

它允许您在定义函数、类或类型时不指定具体的数据类型,而是在使用这些函数类型时再传入实际的数据类型

TypeScript 中,泛型通过使用类型参数 来实现。

泛型能够提高代码的可重用性和灵活性,因为它允许您编写能够处理多种数据类型的通用代码。

类型参数可以在函数、类或类型的定义中使用,以代表一个未知的数据类型。当您使用带有泛型的函数、类或类型时,您需要提供实际的数据类型来替换这些类型参数,从而在特定的上下文中确定具体的数据类型。

类型参数

类型参数的作用类似于函数参数,但它们不是用来传递实际的值,而是用来指定某些类型的 占位符

定义泛型使用,也可以叫作泛型参数

当您在调用泛型函数或实例化泛型类时,会提供实际的类型来替代这些类型参数,从而确定函数的行为或类的属性和方法的类型。

TypeScript 中,使用尖括号(< >)来声明类型参数

在条件类型中,也可以使用 infer 作类型参数补充

// 类型参数T、U
type ExtractSelf3<T, U> = T extends U ? U : T;

// 类型参数T、infer U相当于补充声明类型参数U
type ExtractSelf<T> = T extends infer U ? U : T;

// 报错:Cannot find name 'U'. 只传入类型参数T,未对U进行声明
type ExtractSelf2<T> = T extends U ? U : T;

内置类型

一、基本类型

  • number: 表示数值类型。
  • string: 表示字符串类型。
  • boolean: 表示布尔类型。
  • null: 表示空值或空引用。
  • undefined: 表示未定义的值。
  • void: 表示没有返回值的函数。
  • symbol: 表示唯一的符号值。

二、复合类型

  • Array<T>: 表示数组类型,且是一个由类型 T 的元素组成的数组
  • Tuple: 表示固定长度的数组,允许元素的类型是不同的。
  • Object: 表示任意 JavaScript 对象类型。
  • Function: 表示函数类型。
  • Enum: 表示枚举类型。
  • Class: 表示类类型。

1 - Array<T>数组

申明一个由类型T的元素组成的数组(数组里的元素类型都为T

// 声明变量
const numbers: Array<number> = [1, 2, 3, 4, 5];
const names: Array<string> = ["Alice", "Bob", "Charlie"];

// 函数参数/返回值
function doubleNumbers(numbers: Array<number>): Array<number> {
return numbers.map(num => num * 2);
}
const doubled = doubleNumbers([1, 2, 3, 4]);

// 泛型函数
function reverseArray<T>(array: Array<T>): Array<T> {
return array.reverse();
}
const reversedNumbers = reverseArray([1, 2, 3]);
const reversedStrings = reverseArray(["apple", "banana", "cherry"]);

// T也可以是变量别名,此处为一个二维数组(一维为Point,二维为[number, string]组成的长度为2的数组)
type Point = [number, string];
const coordinates: Array<Point> = [[1, '2'], [3, '4'], [5, '6']];

// 使用Array[number],可直接返回联合类型
type Point2<M extends string[]> = M[number];
type P = Point2<['small', 'medium', 'large']>; // 'small' | 'medium' | 'large'

// eg: 通过Array[number]和模版字符串``定义BEM类型
type IsNever<T> = [T] extends [never] ? true : false
type IsUnion<U> = IsNever<U> extends true ? "" : U
type BEM<B extends string, E extends string[], M extends string[]> = `${B}${IsUnion<`__${E[number]}`>}${IsUnion<`--${M[number]}`>}`

type ClassNames1 = BEM<'btn', ['price'], []> // 'btn__price'
type ClassNames2 = BEM<'btn', ['price'], ['warning', 'success']> // 'btn__price--warning' | 'btn__price--success'
type ClassNames3 = BEM<'btn', [], ['small', 'medium', 'large']> // 'btn--small' | 'btn--medium' | 'btn--large'
type ClassNames4 = BEM<'btn', ['primary', 'danger'], ['small', 'medium', 'large']> // "btn__primary--small" | "btn__primary--medium" | "btn__primary--large" | "btn__danger--small" | "btn__danger--medium" | "btn__danger--large"

2 - Tuple元组 - 特殊数组

申明一个可以由不同类型组成,长度固定的数组

// 定义一个元组类型,包含不同类型的元素
type MixedTuple = [number, string, boolean];

// 声明一个符合 MixedTuple 类型的元组变量
const mixedValues: MixedTuple = [42, "hello", true];

// 访问元组中的元素
const firstElement = mixedValues[0]; // 42
const secondElement = mixedValues[1]; // "hello"
const thirdElement = mixedValues[2]; // true
提示

不限类型不限数组长度,可以使用any[]

3 - Object对象

const person: object = { name: "Alice", age: 30 };
const greet: object = function(name: string) {
console.log(`Hello, ${name}!`);
};

4 - Function函数

// 直接使用别名
type MathOperation = Function;

// 直接函数写法加返回值
const greet = (name: string): void => {
console.log(`Hello, ${name}!`);
};

5 - Enum枚举

将一组相关的命名常量组织起来,使代码更加可读和维护

type Red = 'red';
type Green = 'green';
type Blue = 'blue';

enum Color {
Red,
Green,
Blue,
}

const selectedColor: Color = Color.Green;

if (selectedColor === Color.Green) {
console.log("Selected color is green.");
}

6 - Class类

创建一个类

class Person {
name: string;
age: number;

constructor(name: string, age: number) {
this.name = name;
this.age = age;
}

greet() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
}
}

const alice = new Person("Alice", 30);
alice.greet(); // Output: "Hello, my name is Alice and I'm 30 years old."

三、类型工具

  • Record<K, T>: 创建一个以类型 K 为键,类型 T 为值的对象类型。
  • Readonly<T>: 创建只读版本的类型。
  • Partial<T>: 复制T类型的所有属性,并将所有属变成可选属性
  • Required<T>: 复制T类型的所有属性,并将所有属变成必选属性
  • Pick<T, K>: 子集,从类型 T 中选取指定属性 K
  • Omit<T, K>: 排除,从类型 T 中排除属性 K
  • Exclude<T, U>: 从类型 T 中排除可以赋值给类型 U 的类型。
  • ReturnType<T>: 获取函数类型 T 的返回类型。
  • Union: 用于创建联合类型,例如 number | string。的关系
  • Intersection: 用于创建交叉类型,表示同时具有多个类型的值。的关系

1 - Record<K, T>创建对象

创建一个以类型 K 为键,类型 T 为值的对象类型。

type Person = {
name: string;
age: number;
};

// 使用 Record 创建一个名为 People 的类型,键为 string 类型,值为 Person 类型
type People = Record<string, Person>;

const people: People = {
alice: { name: 'Alice', age: 30 },
bob: { name: 'Bob', age: 25 },
};

console.log(people.alice); // Output: { name: 'Alice', age: 30 }
console.log(people.bob); // Output: { name: 'Bob', age: 25 }

2 - Readonly<T>只读

type Person = {
name: string,
age: number
}

// 申请一个只读类型
type ReadonlyPerson = Readonly<Person>;

const person: ReadonlyPerson = {
name: 'LiLei',
age: 18
}

// 下面的代码将会报错,因为属性是只读的
person.age = 19;

3 - Partial<T>可选

复制T类型的所有属性,并将所有属变成可选属性

type Person = {
name: string;
age: number;
address: string;
};

// 复制 Person 类型中的所有属性,并变为可选
type PartialPerson = Partial<Person>;

// 不会报错
const partialAlice: PartialPerson = {
name: "Alice",
};

// 会报错:Type '{ name: string; }' is missing the following properties from type 'Person': age, address
const alice: Person = {
name: 'Alice'
}

4 - Required<T>必选

Partial<T>相反,复制T类型的所有属性,并将所有属变成必选属性

type PartialPerson = {
name?: string;
age?: number;
address?: string;
};

// 将 PartialPerson 类型中的所有属性变为必需
type RequiredPerson = Required<PartialPerson>;

// 会报错:Type '{ name: string; }' is missing the following properties from type 'Required<PartialPerson>': age, address
const requiredAlice: RequiredPerson = {
name: "Alice",
};

// 不会报错
const alice: Person = {
name: "Alice",
}

5 - Pick<T, K>选属性子集

Pick<T, K>从类型T中选出K, K可以是字符串字面量类型或联合类型

type Person = {
name: string;
age: number;
address: string;
};

// 从 Person 类型中选择 name 和 age 属性
type PersonInfo = Pick<Person, 'name' | 'age'>;

const personInfo: PersonInfo = {
name: 'Alice',
age: 30,
};

6 - Omit<T, K>排除属性子集

Omit<T, K>从类型T中排除出K, 生成新类型, K可以是字符串字面量类型或联合类型

type Person = {
name: string;
age: number;
address: string;
};

// 错误示例:K是字符串字面量类型或联合类型,而不是数组
type InvalidOmit = Omit<Person, ['age', 'address']>;

// 正确写法
type PersonWithoutAgeAndAddress = Omit<Person, 'age' | 'address'>;

7 - Exclude<T, U>排除类型子集

Exclude<T, U>从类型T中排除出可以赋值给U的类型,生成新类型

提示
  • Omit<T, K>只能用于排除属性
  • Exclude<T, U>只能用于排除类型

8 - ReturnType<T>返回值的类型

type MyFunction = () => string;

// 获取 MyFunction 的返回值类型
type ResultType = ReturnType<MyFunction>;

const myFunction: MyFunction = () => {
return "Hello, world!";
};

const result: ResultType = myFunction();
console.log(result); // Output: "Hello, world!"

9 - 联合类型

将多个类型组合在一起,以表示一个值可以是这些类型中的任意一个,使用|连接

// 声明一个联合类型
type NumberOrString = number | string;

// 变量可以是 number 或 string 类型
let value: NumberOrString;

value = 10; // OK,value 是 number 类型
value = "hello"; // OK,value 是 string 类型
value = true; // Error,value 不能是 boolean 类型
// 这也是个联合类型,只是指定了类型值
type labelType = 'template' | 'rich';

let label: labelType;

label = 'template'; // OK
label = 'rich'; // OK
label = 'custom'; // Error,'custom' 不是有效的值
提示

2个映射类型生成的联合类型,不是具有两个映射类型属性合成的属性,而应该理解为:具有映射类型A的所有属性,或者具有映射类型B的所有属性

interface A {
name: string;
age: string;
}

interface B {
name: string;
height: number;
}

type C = A | B; // C可能是{name, age},或者是{name, height},而不是{name, age, height}

// 不报错
const c: C = {
name: 'Alice',
age: "18",
height: 178
}

// 会报错:因为这里不知道c是具有A类型 还是B类型
// c.height

// 方法一:类型判断
if ('height' in c) {
c.height
}

// 方法二:使用as断言
(a as B).height

10 - 交叉类型

将多个类型合并成一个新的类型,新类型将拥有所有参与合并的类型的属性和方法。使用 & 符号连接

// 定义两个类型
type Person = {
name: string;
};

type Employee = {
employeeId: number;
};

// 创建交叉类型,合并两个类型的属性
type EmployeePerson = Person & Employee;

const employeePerson: EmployeePerson = {
name: "John",
employeeId: 123
};

四、特殊类型

  • any: 表示任意类型,允许进行动态类型转换。
  • never: 表示永远不会出现的值的类型,通常用于表示错误或异常情况。
  • unknown: 类似于 any,但更安全,需要进行类型检查或类型断言。
  • this: 表示函数中的 this 上下文类型。

1 - any任意类型

使用 any 类型可以绕过 TypeScript类型检查,但同时也丧失了类型安全性。

应尽量避免使用 any 类型,因为它会减弱类型检查的优势

2 - never永不会出现的类型

// 抛出异常的函数的返回值
function throwError(message: string): never {
throw new Error(message);
}
// 该函数永远不会正常返回,它总是会抛出异常
throwError("Something went wrong");

// 永远无返回值的函数
function infiniteLoop(): never {
while (true) {
// 无限循环,永远不会返回结果
}
}

// 函数检查
type Result<T> = T extends number ? string : never;
const numberValue: Result<number> = "42";
// Error: Type 'string' is not assignable to type 'never'.
const stringValue: Result<string> = "42";

// 联合类型的不可达分支
type Fruit = "apple" | "banana" | "orange";
function getFruitColor(fruit: Fruit): string {
switch (fruit) {
case "apple":
return "red";
case "banana":
return "yellow";
case "orange":
return "orange";
default:
const _exhaustiveCheck: never = fruit;
return _exhaustiveCheck; // Error,该分支是不可达的
}
}

3 - unknow未知类型

使用unknow类型必须进行类型检查或使用类型断言,否则无法正确访问其属性和方法

let userInput: unknown;
userInput = "Hello, TypeScript!";

// 需要进行类型检查或类型断言
if (typeof userInput === "string") {
console.log(userInput.toUpperCase());
}

// 直接访问会报错
console.log(userInput.toUpperCase());

// 也可以使用 as 类型断言
console.log((userInput as string).toUpperCase());

// 将unknow赋值给其他类型, 使用类型断言将 unknown 转换为 string
let value: unknown = "Hello";
let stringValue: string = value as string;

关键字和操作符

类型相关的关键字有:

  • type 别名
  • interface 接口
  • extends 继承/条件类型
  • infer 类型推断
  • in:遍历对象属性的键
  • declare 全局声明
  • class
  • enum 枚举
  • | 联合类型
  • & 交叉类型
  • <> / as 类型断言
  • <T> 泛型

声明属性状态的关键字有:

  • readonly 只读
  • public 公共,默认的状态,可以不写
  • private 私有,只能在的内部访问
  • protected 受保护的,只能在的内部和派生类中访问,不能在类的外部访问
  • static 静态的,属于本身而不是类的实例。静态属性可以在类的内部和外部通过类名访问

类型相关的操作符有:

  • keyof: 获取类型的属性名组成的联合类型
  • typeof: 获取值的类型

一、类型断言

明确指示某个值的类型,从而绕过 TypeScript对该值的类型检查

const value: any = "Hello, TypeScript!";
// 使用<>尖括号
const length: number = (<string>value).length;

// 使用as
const length: number = (value as string).length;

二、type和interface的区别

1 - 语法

  • type T = xxx
  • interface xxx {}
type PersonType = {
name: string;
age: number;
};

interface PersonInterface {
name: string;
age: number;
}

2 - 定义的内容

  • type可以用于定义类、对象的类型,也可以不是
  • interface只能用于定义类、对象的类型

3 - 扩展方式

  • type使用 |联合类型 或 &交叉类型
  • interface使用extends
type Cat = Animal & { breed: string };

interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}

三、extends继承/条件类型

  • typescript中,T extends U可以用作类型继承,也可以用于条件类型的判断
  • T extends U用作条件类型的判断时,有多种解释:
    • T: 非固定值类型,U:非固定值类型 => T是否是U的子类型
    • T: 固定值类型,U:非固定值类型 => T值是否属于U类型
    • T: 固定值类型,U:固定值类型
      • 数组:基于元素的位置和类型比较
      • 对象:基于属性和属性值
      • 其他常量:T值是否等于U
// 类型继承
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}

/************ 条件类型 *********/
// 非固定值类型:看是否是子集
type AA = (number | string) extends string ? true : false; // false
type AAA = string extends (number | string) ? true : false; // true

// 在数组中比较: 基于元素的位置和类型的
// ['','']是否是['','',''] 部分和 ...any 部分
type C = ['', ''] extends ['', '', '', ...any] ? true : false; // false

// ['','','']是否是['',''] 部分和 ...any 部分
type CC = ['', '',''] extends ['', '', ...any] ? true : false; // true


// 对象内:基于属性和属性值
type CCC = { name: 'Alice' } extends { name: 'Alice', age: 18 } ? true : false; // false

type CCCC = {name: 'Alice', age: 18} extends {name: 'Alice'} ? true : false; // true


// 此处传入<4, 6>,`T extends R['length']`判断为4是否等于0
type GreaterThan<T extends number, U extends number, R extends any[] = []> =
T extends R['length']
? false
: U extends R['length']
? true
: GreaterThan<T, U, [...R, 1]>

const varA: GreaterThan<4, 6> = true;

四、infer类型推断

用于条件类型 extends中进行类型的推断操作

  • extends一起使用
    • 部分功能=函数的类型参数,作类型补充
    • 比直接写<>类型参数更友好的一点是:可以用作动态类型参数
    • 可以看作实现类似高阶函数的一根镙丝钉:比如,
      • 类型A的类型参数B,类型参数B又是个带类型参数C的类型,infer可以对C做类型推断
      • 类型A的类型参数B,类型参数B又是个带参数的函数Dinfer可以对D的参数做类型推断
infer做类型补充
// 类型参数T、infer U相当于是U的类型声明
type ExtractSelf<T> = T extends infer U ? U : T;

// 类型参数T、U
type ExtractSelf3<T, U> = T extends U ? U : T;

// 报错:Cannot find name 'U'. 只传入类型参数T,未对U进行声明
type ExtractSelf2<T> = T extends U ? U : T;
infer对类型Promise的类型参数做类型推断
type GetValueType<T> = T extends Promise<infer R> ? R : never;

type PromiseType = Promise<number[]>;

type Res = GetValueType<PromiseType>; // number[]
infer对函数greet的参数做类型推断
type FunctionType<T> = T extends (...args: infer Args) => infer Result ? [Args, Result] : never;

// 这么写,FunctionType<typeof greet>会报错:Generic type 'FunctionType' requires 3 type argument(s).
// type FunctionType<T, Args extends any[], Result> = T extends (...args: Args) => Result ? [Args, Result] : never;

function greet(name: string, age: number): string {
return `Hello, ${name}! You are ${age} years old.`;
}

type GreetFunctionType = FunctionType<typeof greet>; // [ [string, number], string ]

let gt: GreetFunctionType = [['Alice', 18], 'Hello, Alice! You are 18 years old'];

五、in遍历对象属性名

in:遍历对象属性的键

// 定义一个映射类型,遍历 T 的所有属性
type SharedProperties<T> = {
[K in keyof T]: T[K];
};

六、declare全局声明

TypeScript 中,.d.ts 文件用于编写声明文件,以描述 模块、库或对象的类型信息

TypeScript 会自动识别并应用这些声明

// 声明变量
declare const MY_GLOBAL: string;

// 声明模块
declare module "moment" {
import moment from 'moment';
export default moment;
}

// 声明方法
declare function greet(name: string): string;