CC 咖啡猫的工作空间 Coding Space

TypeScript

类型系统

基础类型

// 基础类型
let isDone: boolean = false;
let count: number = 42;
let decimal: number = 3.14;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let big: bigint = 100n;
let name: string = 'Alice';
let sym: symbol = Symbol('key');

// 数组
let list: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3];  // 泛型写法(JSX 中避免冲突)

// 元组(Tuple)
let tuple: [string, number] = ['hello', 42];
tuple[2] // 访问越界索引默认 undefined(strict 下报错)

// 枚举
enum Direction {
  Up,      // 0
  Down,    // 1
  Left,    // 2
  Right,   // 3
}
enum Status {
  Active = 'ACTIVE',
  Inactive = 'INACTIVE',
}
// 枚举编译后会有反向映射(常量枚举 const enum 无反向映射)
Direction.Up;        // 0
Direction[0];        // "Up"

any / unknown / never / void 区别

// any —— 绕过类型检查(应尽量避免)
let anyVal: any = 42;
anyVal = 'string';
anyVal.foo(); // 无类型保护,运行时可能报错

// unknown —— 安全的 any(使用前必须类型收窄)
let unknownVal: unknown = 42;
unknownVal.toFixed(); // Error: Object is of type 'unknown'
if (typeof unknownVal === 'number') {
  unknownVal.toFixed(); // OK(类型收窄后)
}

// never —— 永远不会发生(函数永不返回、穷尽检查)
function throwError(message: string): never {
  throw new Error(message);
}
function infiniteLoop(): never {
  while (true) {}
}

// void —— 函数无返回值
function log(msg: string): void {
  console.log(msg);
  // 可以 return undefined,但不能 return 值
}

// 类型层级:never < literal < 基础类型 < unknown/any

联合类型 / 交叉类型

// 联合类型(|)—— 值可以是其中一种
type Status = 'loading' | 'success' | 'error';
type Result = number | string;

// 交叉类型(&)—— `所有属性合并
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged; // { name: string; age: number; }

// 交叉类型冲突处理
type A = { a: string };
type B = { a: number };
type C = A & B; // a: never(string & number → never)

字面量类型

// 字面量类型(value as type)
let x: 'hello' = 'hello'; // x 只能是 'hello'
type Direction = 'north' | 'south' | 'east' | 'west';

// 模板字面量类型
type EventName = `on${Capitalize<string>}`;
type CSSValue = `${number}px` | `${number}rem`;

类型断言

// 尖括号(JSX 中不能用)
let someValue: unknown = 'string';
let len: number = (<string>someValue).length;

// as(推荐)
let len2: number = (someValue as string).length;

// 非空断言(!)
let maybeStr: string | null | undefined;
maybeStr!.length; // 断言不为 null/undefined

// 双重断言(尽量避免,破坏 type safety)
let x = 'hello' as unknown as number;

接口与类型别名

interface vs type 区别

// 接口(interface)—— 声明合并 + extends
interface User {
  name: string;
}
interface User {
  age: number;  // 同名合并
}
// User → { name: string; age: number }

interface Admin extends User {
  role: 'admin';
}

// 类型别名(type)—— 联合/交叉/元组/映射类型等复杂场景
type ID = string | number;
type Status = 'active' | 'inactive';
type Pair<T> = [T, T];
type Mapped<T> = { [K in keyof T]: boolean };

// 区别总结
// 1. interface 支持声明合并,type 不支持
// 2. type 可以表达联合/交叉/元组/条件类型等,interface 不能
// 3. interface extends 语法更清晰,type 用 &
// 4. 对对象类型优先用 interface,需要联合/映射等用 type

索引签名

interface StringMap {
  [key: string]: string;     // 所有属性必须是 string 类型
}
interface NumberOrString {
  [key: string]: string | number; // 值与索引签名兼容
  length: number;            // 允许特定的非 string 属性
  name: string;
}

// 两种索引(string 和 number)必须兼容
interface Mixed {
  [index: number]: string;   // 数字签名
  [key: string]: string;     // 字符串签名(number → string 必须兼容)
}

函数类型

// 方式一:类型别名
type Fn = (x: number, y: number) => number;

// 方式二:接口
interface FnInterface {
  (x: number, y: number): number;
}

// 函数重载(多个签名)
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
  return a + b;
}

混合类型

// 可调用 + 有属性
interface Counter {
  (start: number): string;  // 可调用
  interval: number;          // 有属性
  reset(): void;
}

function getCounter(): Counter {
  const counter = ((n: number) => 'count: ' + n) as Counter;
  counter.interval = 1000;
  counter.reset = () => {};
  return counter;
}

泛型

泛型基础与约束

// 基础泛型
function identity<T>(arg: T): T {
  return arg;
}
identity<string>('hello');
identity(42); // 类型推断

// 泛型约束(extends)
interface HasLength {
  length: number;
}
function logLength<T extends HasLength>(arg: T): T {
  console.log(arg.length);
  return arg;
}
logLength('hello'); // OK,string 有 length
logLength([1, 2]); // OK
logLength(42); // Error:number 没有 length

// 多泛型参数
function pair<K, V>(key: K, value: V): [K, V] {
  return [key, value];
}

泛型工具类型(内置)

interface User {
  id: number;
  name: string;
  email: string;
  age?: number;
}

// Partial<T> —— 所有属性可选
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; age?: number; }

// Required<T> —— 所有属性必填
type RequiredUser = Required<User>;
// { id: number; name: string; email: string; age: number; }

// Readonly<T> —— 所有属性只读
type ReadonlyUser = Readonly<User>;

// Pick<T, K> —— 选取部分属性
type UserInfo = Pick<User, 'id' | 'name'>;
// { id: number; name: string; }

// Omit<T, K> —— 排除部分属性
type UserWithoutEmail = Omit<User, 'email'>;
// { id: number; name: string; age?: number; }

// Record<K, T> —— 构造对象类型
type PageInfo = Record<'home' | 'about' | 'contact', { title: string }>;
// { home: { title: string }; about: { title: string }; contact: { title: string }; }

// Exclude<T, U> —— 从联合类型 T 排除 U
type T0 = Exclude<'a' | 'b' | 'c', 'a'>; // 'b' | 'c'

// Extract<T, U> —— 从联合类型 T 提取 U
type T1 = Extract<'a' | 'b' | 'c', 'a' | 'f'>; // 'a'

// NonNullable<T> —— 排除 null 和 undefined
type T2 = NonNullable<string | number | null | undefined>; // string | number

// ReturnType<T> —— 获取函数返回类型
type T3 = ReturnType<() => string>; // string
type T4 = ReturnType<<T>() => T>;   // unknown
type T5 = ReturnType<typeof fetchData>;

// Parameters<T> —— 获取函数参数类型
type T6 = Parameters<(a: string, b: number) => void>; // [string, number]

// ConstructorParameters<T> —— 构造函数参数类型
class Foo { constructor(a: string, b: number) {} }
type T7 = ConstructorParameters<typeof Foo>; // [string, number]

// InstanceType<T> —— 实例类型
type T8 = InstanceType<typeof Foo>; // Foo

条件类型

// 基础条件类型:T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<42>;      // false

// 条件类型分配律(Distributive Conditional Types)
// 裸类型参数在条件类型中会被分配
type ToArray<T> = T extends any ? T[] : never;
type C = ToArray<string | number>; // string[] | number[](不是 (string | number)[])

// 阻止分配:用 [] 包裹
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type D = ToArrayNonDist<string | number>; // (string | number)[]

infer 关键字

// infer —— 在条件类型中推断类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type ParamType<T> = T extends (arg: infer P) => any ? P : never;

// 提取 Promise 内部类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type E = UnwrapPromise<Promise<string>>; // string
type F = UnwrapPromise<number>;          // number

// 提取数组元素类型
type ArrayItem<T> = T extends (infer U)[] ? U : never;
type G = ArrayItem<string[]>; // string

// infer 递归(取出深层 Promise)
type DeepUnwrap<T> = T extends Promise<infer U> ? DeepUnwrap<U> : T;
type H = DeepUnwrap<Promise<Promise<Promise<string>>>>; // string

模板字面量类型

type EventName = `on${Capitalize<string>}`;
// 'onChange' | 'onClick' | 'onSubmit' ...

// 内置字符串操作类型
type UppercaseName = Uppercase<'hello'>; // 'HELLO'
type LowercaseName = Lowercase<'HELLO'>; // 'hello'
type Capitalized = Capitalize<'hello'>;  // 'Hello'
type Uncapitalized = Uncapitalize<'Hello'>; // 'hello'

访问修饰符

class Animal {
  public name: string;        // 默认,任何地方可访问
  private age: number;        // 仅类内部(编译时检查,运行时无强制)
  protected type: string;     // 类内部和子类
  readonly id: string;        // 只读(初始化后不可修改)
  #privateField = 42;         // ES2022 原生私有字段(运行时有强制)

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

抽象类

abstract class Shape {
  abstract area(): number;    // 抽象方法(子类必须实现)

  describe(): string {       // 非抽象方法
    return `Area: ${this.area()}`;
  }
}

class Circle extends Shape {
  constructor(private radius: number) {
    super();
  }

  area(): number {
    return Math.PI * this.radius ** 2;
  }
}

实现接口

interface Serializable {
  serialize(): string;
  deserialize(data: string): void;
}

interface Loggable {
  log(): void;
}

class UserModel implements Serializable, Loggable {
  constructor(public name: string) {}

  serialize(): string {
    return JSON.stringify(this);
  }

  deserialize(data: string): void {
    const obj = JSON.parse(data);
    Object.assign(this, obj);
  }

  log(): void {
    console.log(`User: ${this.name}`);
  }
}

this 类型

class Calculator {
  constructor(public value: number = 0) {}

  add(n: number): this {         // 返回 this 实现链式调用
    this.value += n;
    return this;
  }

  multiply(n: number): this {
    this.value *= n;
    return this;
  }

  getResult(): number {
    return this.value;
  }
}

class AdvancedCalculator extends Calculator {
  pow(n: number): this {
    this.value = Math.pow(this.value, n);
    return this;
  }
}

const calc = new AdvancedCalculator();
calc.add(5).multiply(2).pow(3).getResult(); // 类型正确

模块与命名空间

esModuleInterop

// 不开启 esModuleInterop 时:
import * as React from 'react';     // 命名空间导入
import React = require('react');    // 导入 = require

// 开启 esModuleInterop 时:
import React from 'react';          // 默认导入,更自然

esModuleInteroptsconfig.json 中启用,配合 allowSyntheticDefaultImports 让 CJS 和 ES Module 互操作更顺畅。

declare

// 声明全局类型(通常放在 .d.ts 文件中)
declare global {
  interface Window {
    __INITIAL_STATE__: Record<string, unknown>;
  }
}

// 声明模块(用于无类型 npm 包)
declare module 'some-untyped-lib' {
  export function doSomething(): void;
  export const VERSION: string;
}

// 声明变量(已存在的全局变量)
declare const $: (selector: string) => any;

.d.ts 声明文件

// types.d.ts —— 只包含类型声明,不包含实现
// 被 tsconfig.json 的 include 或 files 包含

// 声明一个模块的类型
interface UserData {
  id: number;
  name: string;
}

// 扩展已有类型
declare module '*.css' {
  const classes: Record<string, string>;
  export default classes;
}

declare module '*.svg' {
  const content: React.FC<React.SVGProps<SVGElement>>;
  export default content;
}

三斜线指令

// 用于依赖其他声明文件
/// <reference path="./types.d.ts" />
/// <reference types="node" />         // 引入 @types/node
/// <reference lib="es2015" />         // 引入 lib 声明

// 使用场景:
// 1. 手动引用其他 .d.ts 文件(构建工具未自动包含时)
// 2. 声明文件之间的依赖声明
// 3. 在打包前手动关联类型(非模块化场景)

高级类型

类型守卫

// typeof 守卫
function process(value: string | number) {
  if (typeof value === 'string') {
    return value.toUpperCase();  // 收窄为 string
  }
  return value.toFixed(2);       // 收窄为 number
}

// instanceof 守卫
class Dog { bark() {} }
class Cat { meow() {} }
function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark();
  } else {
    animal.meow();
  }
}

// 自定义类型守卫(is)
interface Fish { swim(): void; }
interface Bird { fly(): void; }

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

function move(pet: Fish | Bird) {
  if (isFish(pet)) {
    pet.swim();  // 类型收窄为 Fish
  } else {
    pet.fly();
  }
}

// in 守卫
if ('swim' in animal) {
  animal.swim(); // 收窄为含 swim 的类型
}

// 可辨别联合
type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'rectangle'; width: number; height: number }
  | { kind: 'triangle'; base: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'rectangle':
      return shape.width * shape.height;
    case 'triangle':
      return (shape.base * shape.height) / 2;
    // default: never —— 穷尽检查
  }
}

映射类型(Mapped Types)

// 基础映射类型
type Readonly<T> = {
  readonly [K in keyof T]: T[K];
};

type Optional<T> = {
  [K in keyof T]?: T[K];
};

// 修饰符:+ / - 添加或移除 readonly 和 ?
type Mutable<T> = {
  -readonly [K in keyof T]: T[K];
};
type Required<T> = {
  [K in keyof T]-?: T[K];
};

// 键名重映射(TypeScript 4.1+)
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
// type GettersUser = { getName: () => string; getAge: () => number; }

// 条件筛选键
type StringKeys<T> = {
  [K in keyof T]: T[K] extends string ? K : never;
}[keyof T];
type OnlyStringProps<T> = Pick<T, StringKeys<T>>;

分布式条件类型

// 裸类型参数在条件类型中自动分配
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>; // string[] | number[]

// 实战:剔除指定类型的属性
type ExcludeType<T, U> = {
  [K in keyof T]: T[K] extends U ? never : T[K];
}[keyof T];
// 等效于内置的 Extract / Exclude

// Filter 工具
type Filter<T, U> = T extends U ? T : never;
type Numbers = Filter<1 | 'a' | 2 | 'b', number>; // 1 | 2

工程实践

tsconfig.json 关键配置

{
  "compilerOptions": {
    // ===== 核心 =====
    "strict": true,
    // 开启以下全部:
    // strictNullChecks: true        —— null/undefined 不可赋值给其他类型
    // strictFunctionTypes: true     —— 函数参数逆变检查
    // strictBindCallApply: true     —— call/apply/bind 参数类型检查
    // strictPropertyInitialization: true —— 类属性必须初始化
    // noImplicitAny: true           —— 禁止隐式 any
    // noImplicitThis: true          —— 禁止 this 隐式 any
    // alwaysStrict: true            —— 总是 use strict

    // ===== 模块 =====
    "module": "ESNext",              // ES Module
    "moduleResolution": "bundler",   // bundler 解析(现代构建工具)
    // 或 "node" / "classic"
    "target": "ES2022",              // 编译目标
    "lib": ["ES2022", "DOM"],

    // ===== 模块解析 =====
    "baseUrl": ".",                  // 基础路径
    "paths": {
      "@/*": ["src/*"]               // 别名映射
    },
    "resolveJsonModule": true,       // 允许 import json
    "allowJs": true,                 // 允许 JS 文件
    "checkJs": false,                // 不检查 JS

    // ===== 输出 =====
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,             // 生成 .d.ts 声明文件
    "declarationMap": true,          // 声明文件 sourcemap
    "sourceMap": true,

    // ===== 互操作 =====
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "isolatedModules": true,         // 确保每个文件可独立编译(对 Babel/esbuild 重要)

    // ===== 质量 =====
    "skipLibCheck": true,            // 跳过 .d.ts 类型检查(加速编译)
    "forceConsistentCasingInFileNames": true,
    "noUnusedLocals": true,          // 未使用局部变量报错
    "noUnusedParameters": true,      // 未使用参数报错
    "noFallthroughCasesInSwitch": true // switch 穿透报错
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

类型声明文件管理

主流方式

  1. 自带类型:库自带 .d.ts 文件(package.jsontypes 字段指向)
  2. @types/:社区维护的类型包(npm i -D @types/react
  3. 自定义声明:项目内的 .d.ts 文件

项目内声明文件组织

// src/types/global.d.ts —— 全局类型扩展
export {};

declare global {
  interface Window {
    __APP_CONFIG__: { apiUrl: string; env: string };
  }
  namespace NodeJS {
    interface ProcessEnv {
      NODE_ENV: 'development' | 'production';
      API_KEY: string;
    }
  }
}

// src/types/modules.d.ts —— 第三方模块类型声明
declare module 'untyped-lib' {
  export function run(config: Record<string, unknown>): Promise<void>;
  export const version: string;
}

// src/types/images.d.ts —— 静态资源类型
declare module '*.svg' {
  import type { FC, SVGProps } from 'react';
  const content: FC<SVGProps<SVGSVGElement>>;
  export default content;
}
declare module '*.module.css' {
  const classes: Record<string, string>;
  export default classes;
}

类型兼容性与逆变/协变

// 协变(Covariance)—— 保持父子关系
// 只读属性:Dog extends Animal → Readonly<Dog> extends Readonly<Animal>
interface Animal { name: string; }
interface Dog extends Animal { bark(): void; }
let animals: Animal[] = [];
let dogs: Dog[] = [];
animals = dogs; // OK(数组是协变的)

// 逆变(Contravariance)—— 反转父子关系
// 函数参数:Dog extends Animal → (Animal) => void extends (Dog) => void
type FnDog = (dog: Dog) => void;
type FnAnimal = (animal: Animal) => void;
let fDog: FnDog = (d) => d.bark();
let fAnimal: FnAnimal = (a) => console.log(a.name);
fDog = fAnimal; // OK(严格模式下:函数参数逆变)
// 安全原因:fDog 期望传入 Dog,fAnimal 接受 Animal(更宽泛),传入 Dog 没问题

strictFunctionTypes 开启后:函数参数逆变(更安全)。

条件类型实战

// 1. 深度提取 API 返回类型
type ApiResponse<T> = { code: number; data: T; message: string };
type ExtractData<T> = T extends ApiResponse<infer D> ? D : never;
type UserResponse = ApiResponse<{ id: number; name: string }>;
type UserData = ExtractData<UserResponse>; // { id: number; name: string }

// 2. 函数参数中的第一个参数类型
type FirstArg<T> = T extends (a: infer A, ...rest: any[]) => any ? A : never;

// 3. 联合类型转换为交叉类型(Union to Intersection)
type UnionToIntersection<U> =
  (U extends any ? (k: U) => void : never) extends
  (k: infer I) => void ? I : never;
type T = UnionToIntersection<{ a: 1 } | { b: 2 }>;
// { a: 1 } & { b: 2 }