第一阶段:基础核心

基础类型 (Basic Types)

基础类型是构建所有更复杂类型的基础

  • 核心类型:string, number, boolean, null, undefined, symbol, bigint
    • 示例:const username: string = "Alice"
  • 特殊类型:any, unknown, void, never
    • any(任意类型):全集,谨慎使用。它告诉 TypeScript "放弃类型检查"
      • 场景:从旧的 JavaScript 项目逐步迁移时,作为临时方案;或者处理你完全无法预知其结构的第三方库数据
      • 警告:any 应该作为最后的选择,使用它会使 TypeScript 失去意义
    • unknown(未知类型):全集,any 的安全替代品。它表示"类型未知,在检查之前不准操作"
      • 场景:处理所有外部数据时的最佳实践,如 API 响应、用户输入、文件内容等。它强制之后进行类型检查,使代码更安全
      • 示例:const response: unknown = JSON.parse(apiData)
    • void:用于表示一个函数没有任何返回值(返回 undefined
      • 示例:function logMessage(message: string): void { console.log(message); }
    • never:空集,表示决不可能

数组 (Arrays)、元组 (Tuples)

  • 数组 (type[] or Array<type>)
    • 解释:表示一个包含同一种类型元素的列表,长度不限
    • 示例:const productIds: number[] = [101, 102, 105]
  • 元组 ([type1, type2, ...])
    • 解释:一个固定长度每个位置的类型都已确定的数组
    • 示例:
      • 表示 HTTP 状态:const httpStatus: [number, string] = [200, "OK"]
      • React 的 useState Hook 的返回值:const [count, setCount] = useState(0)

函数 (Functions)

为函数的参数返回值提供类型定义

function foo(a: number, b?: string): string {
    return `${a}${b}`
}
 
const bar: (a: number, b?: string) => void = (a, b) => {}

为你写的每一个函数都添加类型

  • 确保输入正确:防止调用者传入错误类型的数据
  • 确保输出可靠:让使用者清楚地知道函数会返回什么
  • 提供绝佳的编辑器智能提示,极大地提升开发效率和代码质量

对象、接口 (Interfaces)、类型别名 (Type Aliases)

接口和类型别名都可以描述对象结构,但有细微差别和侧重点

  • 接口 (interface)
    • 解释:为对象定义一个”契约”或”蓝图”。它可以被类实现 (implements) 和被其他接口继承 (extends),更符合面向对象的思想
    • 场景:==优先使用 interface 来描述数据结构==。比如 API 的响应数据、函数期望接收的对象参数等
    • 示例:
    interface Product {
        readonly id: number;
        name: string;
        price: number;
        description?: string;
    }
  • 类型别名 (type)
    • 解释:为一个类型创建一个新的名字。它更通用,不仅能描述对象,还能为任何类型(如联合类型、字面量类型)创建别名
    • 场景:
      1. 定义联合类型:变量可以是多种类型之一 type StringOrNumber = string | number;
      2. 定义字面量类型:变量的值只能是一组特定的字符串或数字 type Status = "loading" | "success" | "error";
      3. 为复杂的类型命名:免于重复书写冗长的类型定义

第二阶段:进阶概念

联合类型 (Union Types)、交叉类型 (Intersection Types)

  • 联合类型 (|)
    • 解释:并集。表示一个值可以是几种类型之一
    • 示例:function printId(id: number | string) { ... }
  • 交叉类型 (&)
    • 解释:交集。将多个类型合并为一个类型,新类型将拥有所有成员的特性
    • 场景:需要组合多个已有的类型定义来创建一个更复杂的类型
    • 示例:type AdminUser = User & { permissions: string[]; };

类型断言 (Type Assertions)、类型守卫 (Type Guards)

  • 类型断言 (as)
    • 解释:当你比 TypeScript 更清楚某个值的类型时,可以”告诉”编译器它的类型。这不会改变运行时的行为,仅仅是在编译时起作用
    • 场景:在处理 unknownany 类型时,通过自己的逻辑检查确定了其具体类型
    • 示例:const name = (response as { name: string }).name;
    • 警告:不要滥用类型断言,它可能会隐藏真正的类型错误
  • 类型守卫:
    • 解释:在代码的某个分支中,通过逻辑判断来缩小变量的类型范围
    • 场景:在处理联合类型时,需要根据具体类型执行不同逻辑。
    • 示例:
    if (typeof id === 'string') {
        id.toUpperCase();
    }
     
    if (res instanceof Array) {
        text = res.join(',')
    }

字面量类型 (Literal Types)、枚举 (Enums)

  • 字面量类型
    • 解释:将变量的类型限制为某个或某几个具体的字符串或数字。它通常与联合类型一起使用
    • 示例:function setAlign(align: "left" | "center" | "right") { ... }
  • 枚举 (enum)
    • 解释:用于定义一组命名的常量集合
    • 场景:当你有一组相关的常量,并希望给它们赋予一个语义化的名字时。
    • 示例:使用 Direction.Up 比使用数字 0 更清晰易懂
    enum Direction {
        Up,
        Down,
        Left,
        Right
    }

类 (Classes)

TypeScript 对 ES6 class 的增强,加入了类型注解和访问修饰符

访问修饰符:

  • public:(默认)任何地方都可以访问
  • private:只能在类的内部访问
  • protected:只能在类及其子类的内部访问
  • readonly:属性只能在声明时或构造函数中被初始化,之后不可修改

在使用面向对象编程(OOP)范式来组织代码时,class 是核心

第三阶段:高级与泛型

泛型 (Generics)

泛型是 TypeScript 的一个核心特性,它允许编写可重用的组件。可以把它想象成一个类型的”占位符”或”变量”,在使用它时才传入具体的类型

要解决的问题:避免为不同类型重复编写相同的逻辑,同时又不像 any 那样丢失类型信息

// 尝试 1: 使用 any,丢失了类型信息
function identityAny(arg: any): any {
    return arg;
}
const outputAny = identityAny("hello"); // outputAny 的类型是 any
 
// 解决方案: 使用泛型
function identity<T>(arg: T): T {
	return arg;
}
const output = identity("hello"); // output 的类型被正确推断为 string

核心场景

  • 泛型函数:创建可重用的、类型安全的函数
// 场景:创建一个函数,它的输入和输出类型可以根据调用时传入的类型动态确定
function createSuccessResponse<T>(data: T) {
	return { success: true, data: data };
}
const userResponse = createSuccessResponse({ id: 1, name: "Alice" });
// userResponse.data.id 具有正确的类型提示
  • 泛型接口/类:创建可重用的数据结构
// 场景:定义一个分页查询的返回结果,其中列表项的类型是可变的
interface PaginatedResult<T> {
	items: T[];
	total: number;
}
  • 泛型约束 (extends):限制泛型 T 必须满足某些条件
// 场景:确保传入的参数一定有 .length 属性
function logLength<T extends { length: number }>(arg: T): void {
    console.log(arg.length);
}
 
logLength("hello"); // OK
logLength(123);     // Error

高级类型 (Advanced Types)

这些类型工具可以让你对已有类型进行变换、组合和提取

keyof

获取一个类型的所有,并返回一个由这些键组成的字符串字面量联合类型

function getProperty<T, K extends keyof T>(obj: T, key: K) {
	return obj[key]; // key 被约束为 obj 中存在的键
}

typeof

在类型上下文中,获取一个变量或属性的类型

场景:想从一个已存在的 JavaScript 对象中创建类型,而不想手动重复定义时

const AppConfig = { apiPrefix: "/api", maxRetries: 3 };
type Config = typeof AppConfig; // { apiPrefix: string; maxRetries: number; }

映射类型 (Mapped Types)

基于一个已有的类型,通过规则转换它的每一个属性,从而创建一个新类型

语法:[P in K]: T,类似 for…in 循环

示例(实现 Readonly):

type MyReadonly<T> = {
	readonly [P in keyof T]: T[P];
};

条件类型 (Conditional Types)

让类型根据一个条件在两种类型中选择一种

语法:SomeType extends OtherType ? TrueType : FalseType;

infer 关键字:在 extends 条件中,可以捕获推断出的类型

示例(解包数组元素的类型):

type Flatten<T> = T extends (infer I)[] ? I : T;
type Str = Flatten<string[]>; // string
type Num = Flatten<number>;   // number

工具类型 (Utility Types)

TypeScript 内置了很多方便的、基于泛型和高级类型实现的工具类型,可以直接使用,极大地提高开发效率

Partial<T>

让属性都变成可选的

type A = { a: number; b: string; }
type A1 = Partial<A> // { a?: number; b?: string; }

Required<T>

让属性都变成必选

type A = { a?: number; b?: string; }
type A1 = Required<A> // { a: number; b: string; }

Pick<T, K>

保留选择的属性,K 代表要保留的属性键值

type A = { a: number; b: string; c: boolean }
type A1 = Pick<A, 'a' | 'b'> // { a: number; b: string; }

Omit<T, K>

排除选择的属性,K 代表要排除的属性键值

type A = { a: number; b: string; }
type A1 = Omit<A, 'a'> // { b: string }

Record<K, V>

K 代表键值的类型,V 代表值的类型

type A = Record<string, number> // 等价 { [k: string]: number }

Exclude<T, U>

过滤 T 中和 U 相同(或兼容)的类型

type A1 = Exclude<number | string, string | number[]> // number
 
// 兼容
type A2 = Exclude<number | string, any> // never, 因为 any 兼容任何类型

Extract<T, U>

提取 T 中和 U 相同(或兼容)的类型

type A1 = Extract<number | string, string | number[]> // string

NonNullable<T>

剔除 T 中的 undefinednull

type A1 = NonNullable<number | string | null | undefined> // number | string

Parameters<T>

获取函数类型 T 的参数类型,返回类型为元祖,元素顺序同参数顺序

type A1 = Parameters<(a: number, b: string) => void> // [number, string]

ReturnType<T>

获取函数类型 T 的返回值的类型

type A1 = ReturnType<() => number> // number

InstanceType<T>

T 如果是构造函数类型,那么 InstanceType 可以返回他的实例类型

interface A {
    a: HTMLElement;
}
 
interface AConstructor {
    new(): A;
}
 
function create(AClass: AConstructor): InstanceType<AConstructor> {
    return new AClass();
}

ConstructorParameters<T>

获取构造函数 T 的参数类型,和 Parameters<T> 类似,只是这里 T 是构造函数类型

interface AConstructor {
    new(a: number): string[];
}
 
type A1 = ConstructorParameters<AConstructor> // number

第四阶段:工程实践

模块化 (Modules)

TypeScript 遵循 ES6 模块标准,使用 importexport 来组织代码结构。在任何非 “Hello World” 的项目中,都应该使用模块化来拆分代码,保持代码的清晰和可维护性

声明文件 (Declaration Files)

后缀为 .d.ts 的文件,它不包含实现,只包含类型定义

场景:使用一个纯 JavaScript 编写的库时,它本身没有类型信息。为了在 TypeScript 项目中获得这个库的类型检查和智能提示,需要为其提供一个声明文件。很多流行的库都有社区维护的 @types/... 包,可以直接安装

tsconfig.json 解析

TypeScript 编译器的配置文件,它告诉编译器如何检查和编译你的代码

重要选项:

  • "target":编译后输出的 JavaScript 版本
  • "module":编译后使用的模块系统
  • "strict":开启所有严格类型检查选项,强烈建议始终开启
  • "paths":设置路径别名,简化模块导入路径
  • "lib":指定项目中可以使用的标准库