TypeScript进阶学习

面向对象

类的修饰符

  • public:无访问限制
  • protected:仅当前类中的方法或子类中的方法能访问
  • private: 仅当前类的方法能访问,子类和外部都不行

类的继承

可以使用 extends 复用父类的逻辑和属性

抽象类

  • abstract关键字修饰的类,不能直接实例化,只能被子类继承;
  • 抽象类中用abstract修饰的方法,只有方法签名,无函数体,子类必须实现;
  • 核心作用是统一子类的接口规范,同时复用一些通用逻辑

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 抽象类:用 abstract 修饰,不能直接实例化
abstract class Shape {
// 普通属性:可复用(有具体值)
public color: string;

constructor(color: string) {
this.color = color;
}

// 普通方法:有具体实现,子类可直接复用
public showColor() {
console.log(`这个图形的颜色是:${this.color}`);
}

// 抽象方法:只有签名,无函数体,子类必须实现
abstract getArea(): number; // 计算面积
abstract getPerimeter(): number; // 计算周长
}

// 子类:圆形(继承抽象类 Shape)
class Circle extends Shape {
// 子类独有的属性
private radius: number;

constructor(color: string, radius: number) {
// 调用父类构造函数(抽象类也有构造函数)
super(color);
this.radius = radius;
}

// 必须实现抽象方法1:计算圆的面积
getArea(): number {
return Math.PI * this.radius **2;
}

// 必须实现抽象方法2:计算圆的周长
getPerimeter(): number {
return 2 * Math.PI * this.radius;
}
}

类实现接口

关键字implements:遵守规范,约束类的实现

接口 VS 抽象类

  • 接口interface:仅定义规范,无任何逻辑,class可以implements多个接口
  • 抽象类:既定义规范(抽象方法),又提供可复用逻辑(普通方法)

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 接口:仅定义“契约”(属性/方法签名),无具体实现
interface Flyable {
speed: number;
fly(): void; // 只定义方法签名,无函数体
}

// 类:implements 实现 Flyable 接口
class Bird implements Flyable {
// 必须实现接口的所有属性
speed: number;

constructor(speed: number) {
this.speed = speed;
}

// 必须实现接口的所有方法(函数体自己写)
fly() {
console.log(`鸟以 ${this.speed} km/h 的速度飞行`);
}
}

// 使用
const sparrow = new Bird(30);
sparrow.fly(); // 鸟以 30 km/h 的速度飞行

泛型类

在类名后用 声明 “类型变量”,类的属性、方法、构造函数可使用这个类型变量,实例化时再传入具体类型

类型收窄

类型收窄:通过代码逻辑(如条件判断)让 TS 自动推导变量的更具体类型(比如从 unknown 收窄到 string,从 string | number 收窄到 string),本质是缩小类型范围

方法 适用场景 示例
typeof 原始类型(string/number/boolean…) if (typeof x === "string") { x.length }
instanceof 类/构造函数创建的对象(Array、Date、自定义类) if (x instanceof Array) { x.push(1) }
in 对象属性存在判断,区分不同对象类型 if ("name" in x) { console.log(x.name) }
等值判断 字面量、联合类型(=== / !==) if (x === "admin") { ... }
类型守卫函数 自定义接口/复杂对象 function isUser(x: unknown): x is User { ... }
真值判断 排除 null/undefined/假值 if (x) { x.doSomething() }

自定义类型守卫函数

类型守卫函数:自定义收窄逻辑
通过返回 value is Type 的自定义函数(类型守卫),TS 会按照函数逻辑收窄类型(最灵活的场景)。

1
2
3
4
5
6
7
8
9
10
11
12
// 定义类型守卫函数:判断是否为数字
function isNumber(value: unknown): value is number {
return typeof value === "number" && !isNaN(value);
}

// 初始类型:unknown
let val: unknown = 123;

// 使用类型守卫
if (isNumber(val)) {
console.log(val * 2); // ✅ 收窄为 number
}

更好的类型收窄

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
interface User {
name: string;
age: number;
occupation: string;
}

interface Admin {
name: string;
age: number;
role: string;
}

export type Person = User | Admin;

export const persons: Person[] = [
{
name: 'Max Mustermann',
age: 25,
occupation: 'Chimney sweep'
},
{
name: 'Jane Doe',
age: 32,
role: 'Administrator'
},
{
name: 'Kate Müller',
age: 23,
occupation: 'Astronaut'
},
{
name: 'Bruce Willis',
age: 64,
role: 'World saver'
}
];

export function logPerson(person: Person) {
let additionalInformation: string;
if (person.role) { // ❌ 应该改为 'role' in person来判断,这种方式下ts才会组做类型收窄
additionalInformation = person.role;
} else {
additionalInformation = person.occupation;
}
console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);
}

persons.forEach(logPerson);

特殊类型

交叉类型(&)

简单理解就是合并两个属性

  • 若属性是基本类型:结果为两个类型的交叉(通常是 never)
  • 若属性是对象类型:会递归合并该属性的类型

联合类型(|)

允许一个变量是多种类型中的一种

never类型

never 代表永远不会有返回值、永远不会被执行到 或 永远无法匹配的类型,是 TS 中 “空” 的极致表达。

  1. 场景:抛出错误的函数:永远不会正常返回

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function throwError(message: string): never {
    throw new Error(message);
    // 后面的代码永远执行不到,返回值类型是 never
    }

    // 2. 无限循环的函数:永远不会结束
    function infiniteLoop(): never {
    while (true) {}
    }
  2. 场景:联合类型的 “空子集”

    1
    2
    3
    4
    5
    // 1. 互斥类型交叉 → never(没有值能同时是 string 和 number)
    type Impossible = string & number; // never

    // 2. 空联合类型 → never
    type EmptyUnion = never | never; // never

unknown类型

unknown 类型可以理解成是一个更安全的 any 类型,任何类型可赋值给 unknown
与 any 不同的是,在将 unknown 类型赋值给其他类型之前,必须先进行类型检查或类型断言

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 模拟接口请求函数(返回值类型未知)
async function fetchData(url: string): Promise<unknown> {
const res = await fetch(url);
return res.json(); // 响应体可能是任意类型
}

// 安全处理返回值
async function handleData() {
const data = await fetchData("/api/user");

// 第一步:判断是否是对象(排除 null)
if (typeof data === "object" && data !== null) {
// 第二步:判断是否包含 name 属性
if ("name" in data && typeof (data as { name: unknown }).name === "string") {
// 安全访问 name 属性
console.log("用户名:", (data as { name: string }).name);
}

// 第三步:判断是否包含 age 属性
if ("age" in data && typeof (data as { age: unknown }).age === "number") {
console.log("年龄:", (data as { age: number }).age);
}
}
}

映射类型

基本语法:{ [K in Keys]: T }
其实就类似 js 中的 for in语句,用于遍历 Keys 中的所有类型

比较常见的语法

1
2
3
4
5
6
{ [ P in K ] : T }
{ [ P in K ] ?: T }
{ [ P in K ] -?: T }
{ readonly [ P in K ] : T }
{ readonly [ P in K ] ?: T }
{ -readonly [ P in K ] ?: T }

利用映射类型实现的utility type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};

interface Person {
   name: string;
   age: number;
   location: string;
}

type LazyPerson = Getters<Person>;
// {
//   getName: () => string;
//   getAge: () => number;
//   getLocation: () => string;
// }

条件类型

基本语法:T extends U ? X : Y

常见用法:

1
2
3
4
5
6
type IsString<T> = T extends string ? true : false;

type I0 = IsString<number>;  // false
type I1 = IsString<"abc">;  // true
type I2 = IsString<any>;  // boolean
type I3 = IsString<never>;  // never

特殊关键字

extends关键字

主要使用场景

  • 类继承
  • 接口继承
  • 泛型约束
  • 条件类型

泛型约束使用场景:

1
2
3
4
5
6
7
8
9
10
11
12
// 约束 K 必须是 T 的键(keyof T 是 T 所有键的联合类型)
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}

const product = { name: "手机", price: 2999, stock: 100 };
// ✅ K 是 "price"(属于 keyof product)
getProperty(product, "price"); // 2999

// ❌ K 是 "color"(不属于 keyof product)
getProperty(product, "color");
// 报错:Argument of type '"color"' is not assignable to parameter of type '"name" | "price" | "stock"'

infer关键字

infer = 类型层面的 “变量”,仅能在 T extends U ? X : Y 这种条件类型中使用
作用是「捕获 / 提取」复杂类型的某一部分(比如函数返回值、数组元素类型),并给这部分类型起一个变量名供后续使用

提取数组元素类型
1
2
type ArrayItem<T> = T extends (infer U)[] ? U : never;
// 这里T extends (infer U)[] ? U : never;的意思是,如果T是某个待推断类型的数组,则返回推断的类型,否则返回never
提取函数返回值类型
1
2
3
4
5
type GetReturn<T> = T extends (...args: any[]) => infer R ? R : never;

// 使用
type Fn = () => { name: string; age: number };
type FnReturn = GetReturn<Fn>; // { name: string; age: number }
React 中infer的使用

useReducer 举例,如果我们这样使用 useReducer

1
2
3
const reducer = (x: number) => x + 1;
const [state, dispatch] = useReducer(reducer, '');
// Argument of type "" is not assignable to parameter of type 'number'.

这里useReducer会报一个类型错误,说””不能赋值给number类型

那React这里是如何通过reducer函数的类型来判断state的类型的?

直接看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
function useReducer<R extends Reducer<any, any>, I>(
 reducer: R,
 // ReducerState 推断类型
 initializerArg: I & ReducerState<R>,
 initializer: (arg: I & ReducerState<R>) => ReducerState<R>
): [ReducerState<R>, Dispatch<ReducerAction<R>>];

// infer推断
type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any>
 ? S
: never;
// Reducer类型
type Reducer<S, A> = (prevState: S, action: A) => S;

in关键字

特殊操作符

! 非空断言操作符

+- 修饰符

Ts challenge

字段全改字符串

1
2
3
4
5
6
7
8
type Demo1 = {
a: number;
b: number;
}

type NewDemo1 = {
[P in keyof Demo1]: string;
}

字段全改函数,函数返回值是字段原本类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Demo1 = {
name: number;
age: string;
}

type NewDemo1 = {
[P in keyof Demo1]: () => Demo1[P];
}

// 改成 {getName: () => number, getAge: () => string} 这种形式
type NewDemo1 = {
[P in keyof Demo1 as `get${Capitalize<P>}`]: () => Demo1[P];
}

实现一个Optional(把一个类型里的部分字段变为可选)

1
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

实现一个GetOptional(得到某个类型里面所有可选字段)

1
type GetOptional<T> = [P in keyof T]

只保留对象类型中值为 string 类型的键值对

1
2
3
type PickStringValues<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K]
}