TypeScript 类型系统详细指南

目录

  1. 基础类型
  2. 对象类型
  3. 数组和元组
  4. 函数类型
  5. 联合类型和交叉类型
  6. 字面量类型
  7. 枚举类型
  8. 泛型
  9. 条件类型
  10. 映射类型
  11. 工具类型
  12. 类型断言和类型守卫
  13. 模块和命名空间
  14. 高级类型技巧

基础类型

原始类型

// 布尔类型
let isDone: boolean = false;

// 数字类型
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;

// 字符串类型
let color: string = "blue";
let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${fullName}. I'll be ${
  age + 1
} years old next month.`;

// 空值类型
let unusable: void = undefined;

// Null 和 Undefined
let u: undefined = undefined;
let n: null = null;

// Never 类型 - 表示永不存在的值的类型
function error(message: string): never {
  throw new Error(message);
}

// Any 类型 - 任意类型
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false;

// Unknown 类型 - 类型安全的 any
let value: unknown = 42;
value = "hello";
value = true;

Symbol 和 BigInt

// Symbol 类型
let sym1: symbol = Symbol();
let sym2: symbol = Symbol("key");

// BigInt 类型
let big: bigint = 100n;

对象类型

接口定义

// 基础接口
interface Person {
  name: string;
  age: number;
}

// 可选属性
interface SquareConfig {
  color?: string;
  width?: number;
}

// 只读属性
interface Point {
  readonly x: number;
  readonly y: number;
}

// 索引签名
interface StringArray {
  [index: number]: string;
}

interface StringDictionary {
  [key: string]: string;
}

// 函数类型接口
interface SearchFunc {
  (source: string, subString: string): boolean;
}

// 类类型接口
interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}

class Clock implements ClockInterface {
  currentTime: Date = new Date();
  setTime(d: Date) {
    this.currentTime = d;
  }
  constructor(h: number, m: number) {}
}

类型别名

// 基础类型别名
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;

// 对象类型别名
type User = {
  name: string;
  age: number;
  email?: string;
};

// 函数类型别名
type EventHandler = (event: Event) => void;

数组和元组

数组类型

// 数组类型的两种写法
let list1: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3];

// 只读数组
let ro: ReadonlyArray<number> = [1, 2, 3, 4];

// 多维数组
let matrix: number[][] = [
  [1, 2],
  [3, 4],
];

元组类型

// 基础元组
let x: [string, number];
x = ["hello", 10]; // 正确
// x = [10, "hello"]; // 错误

// 可选元组元素
type StringNumberPair = [string, number?];

// 剩余元素
type StringNumberBooleans = [string, number, ...boolean[]];
type StringBooleansNumber = [string, ...boolean[], number];
type BooleansStringNumber = [...boolean[], string, number];

// 只读元组
type ReadonlyStringNumberPair = readonly [string, number];

// 命名元组
type Range = [start: number, end: number];

函数类型

函数声明和表达式

// 函数声明
function add(x: number, y: number): number {
  return x + y;
}

// 函数表达式
let myAdd = function (x: number, y: number): number {
  return x + y;
};

// 箭头函数
let myAdd2 = (x: number, y: number): number => x + y;

// 完整的函数类型
let myAdd3: (x: number, y: number) => number = function (
  x: number,
  y: number
): number {
  return x + y;
};

可选参数和默认参数

// 可选参数
function buildName(firstName: string, lastName?: string): string {
  if (lastName) {
    return firstName + " " + lastName;
  } else {
    return firstName;
  }
}

// 默认参数
function buildName2(firstName: string, lastName = "Smith"): string {
  return firstName + " " + lastName;
}

// 剩余参数
function buildName3(firstName: string, ...restOfName: string[]): string {
  return firstName + " " + restOfName.join(" ");
}

函数重载

// 函数重载声明
function pickCard(x: { suit: string; card: number }[]): number;
function pickCard(x: number): { suit: string; card: number };

// 函数实现
function pickCard(x: any): any {
  if (typeof x == "object") {
    let pickedCard = Math.floor(Math.random() * x.length);
    return pickedCard;
  } else if (typeof x == "number") {
    let pickedSuit = Math.floor(x / 13);
    return { suit: suits[pickedSuit], card: x % 13 };
  }
}

联合类型和交叉类型

联合类型

// 基础联合类型
type StringOrNumber = string | number;

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}'.`);
}

// 字面量联合类型
type Easing = "ease-in" | "ease-out" | "ease-in-out";

// 判别联合类型
interface Square {
  kind: "square";
  size: number;
}

interface Rectangle {
  kind: "rectangle";
  width: number;
  height: number;
}

interface Circle {
  kind: "circle";
  radius: number;
}

type Shape = Square | Rectangle | Circle;

function area(s: Shape) {
  switch (s.kind) {
    case "square":
      return s.size * s.size;
    case "rectangle":
      return s.height * s.width;
    case "circle":
      return Math.PI * s.radius ** 2;
  }
}

交叉类型

// 基础交叉类型
interface ErrorHandling {
  success: boolean;
  error?: { message: string };
}

interface ArtworksData {
  artworks: { title: string }[];
}

interface ArtistsData {
  artists: { name: string }[];
}

// 交叉类型组合
type ArtworksResponse = ArtworksData & ErrorHandling;
type ArtistsResponse = ArtistsData & ErrorHandling;

// Mixin 模式
function extend<First, Second>(first: First, second: Second): First & Second {
  const result: Partial<First & Second> = {};
  for (const prop in first) {
    if (first.hasOwnProperty(prop)) {
      (result as First)[prop] = first[prop];
    }
  }
  for (const prop in second) {
    if (second.hasOwnProperty(prop)) {
      (result as Second)[prop] = second[prop];
    }
  }
  return result as First & Second;
}

字面量类型

字符串字面量类型

// 字符串字面量类型
type EventNames = "click" | "scroll" | "mousemove";

function handleEvent(ele: Element, event: EventNames) {
  // ...
}

// 模板字面量类型
type World = "world";
type Greeting = `hello ${World}`; // "hello world"

type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
// "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"

数字字面量类型

// 数字字面量类型
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;

function rollDice(): DiceRoll {
  return (Math.floor(Math.random() * 6) + 1) as DiceRoll;
}

布尔字面量类型

// 布尔字面量类型
type Success = true;
type Failure = false;
type Result = Success | Failure;

枚举类型

数字枚举

// 基础数字枚举
enum Direction {
  Up = 1,
  Down,
  Left,
  Right,
}

// 自动递增
enum Response {
  No = 0,
  Yes = 1,
}

// 计算成员
enum FileAccess {
  None,
  Read = 1 << 1,
  Write = 1 << 2,
  ReadWrite = Read | Write,
  G = "123".length,
}

字符串枚举

// 字符串枚举
enum Direction2 {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT",
}

常量枚举

// 常量枚举
const enum Enum {
  A = 1,
  B = A * 2,
}

泛型

基础泛型

// 泛型函数
function identity<T>(arg: T): T {
  return arg;
}

// 使用泛型
let output = identity<string>("myString");
let output2 = identity("myString"); // 类型推断

// 泛型数组
function loggingIdentity<T>(arg: T[]): T[] {
  console.log(arg.length);
  return arg;
}

// 或者
function loggingIdentity2<T>(arg: Array<T>): Array<T> {
  console.log(arg.length);
  return arg;
}

泛型接口

// 泛型接口
interface GenericIdentityFn<T> {
  (arg: T): T;
}

function identity2<T>(arg: T): T {
  return arg;
}

let myIdentity: GenericIdentityFn<number> = identity2;

泛型类

// 泛型类
class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
  return x + y;
};

泛型约束

// 泛型约束
interface Lengthwise {
  length: number;
}

function loggingIdentity3<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

// 在泛型约束中使用类型参数
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); // 正确
// getProperty(x, "m"); // 错误

条件类型

基础条件类型

// 条件类型语法: T extends U ? X : Y
type TypeName<T> = T extends string
  ? "string"
  : T extends number
  ? "number"
  : T extends boolean
  ? "boolean"
  : T extends undefined
  ? "undefined"
  : T extends Function
  ? "function"
  : "object";

type T0 = TypeName<string>; // "string"
type T1 = TypeName<"a">; // "string"
type T2 = TypeName<true>; // "boolean"
type T3 = TypeName<() => void>; // "function"
type T4 = TypeName<string[]>; // "object"

分布式条件类型

// 分布式条件类型
type Diff<T, U> = T extends U ? never : T;
type Filter<T, U> = T extends U ? T : never;

type T5 = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"
type T6 = Filter<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c"

infer 关键字

// infer 推断类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

type T7 = ReturnType<() => string>; // string
type T8 = ReturnType<(s: string) => void>; // void

// 推断数组元素类型
type Flatten<T> = T extends (infer U)[] ? U : T;
type T9 = Flatten<string[]>; // string
type T10 = Flatten<number>; // number

映射类型

基础映射类型

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

type Partial<T> = {
  [P in keyof T]?: T[P];
};

type Required<T> = {
  [P in keyof T]-?: T[P];
};

// 使用示例
interface Person {
  name: string;
  age: number;
  location?: string;
}

type ReadonlyPerson = Readonly<Person>;
type PartialPerson = Partial<Person>;
type RequiredPerson = Required<Person>;

键重映射

// 键重映射
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

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

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

// 过滤键
type RemoveKindField<T> = {
  [K in keyof T as Exclude<K, "kind">]: T[K];
};

interface Circle2 {
  kind: "circle";
  radius: number;
}

type KindlessCircle = RemoveKindField<Circle2>;
// { radius: number; }

工具类型

内置工具类型

// Partial<T> - 将所有属性变为可选
type PartialUser = Partial<User>;

// Required<T> - 将所有属性变为必需
type RequiredUser = Required<User>;

// Readonly<T> - 将所有属性变为只读
type ReadonlyUser = Readonly<User>;

// Pick<T, K> - 选择指定属性
type UserNameAndAge = Pick<User, "name" | "age">;

// Omit<T, K> - 排除指定属性
type UserWithoutEmail = Omit<User, "email">;

// Exclude<T, U> - 从联合类型中排除
type T11 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"

// Extract<T, U> - 从联合类型中提取
type T12 = Extract<"a" | "b" | "c", "a" | "f">; // "a"

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

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

// ConstructorParameters<T> - 获取构造函数参数类型
type T15 = ConstructorParameters<ErrorConstructor>; // [message?: string]

// ReturnType<T> - 获取函数返回类型
type T16 = ReturnType<() => string>; // string

// InstanceType<T> - 获取构造函数实例类型
type T17 = InstanceType<ErrorConstructor>; // Error

// ThisParameterType<T> - 获取函数 this 参数类型
function toHex(this: Number) {
  return this.toString(16);
}
type T18 = ThisParameterType<typeof toHex>; // Number

// OmitThisParameter<T> - 移除函数 this 参数
type T19 = OmitThisParameter<typeof toHex>; // () => string

自定义工具类型

// 深度只读
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

// 深度可选
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

// 获取可选属性
type OptionalKeys<T> = {
  [K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];

// 获取必需属性
type RequiredKeys<T> = {
  [K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];

// 函数重载
type Overload = {
  (foo: string): string;
  (foo: number): number;
};

类型断言和类型守卫

类型断言

// 角括号语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

// as 语法(推荐)
let strLength2: number = (someValue as string).length;

// 非空断言操作符
function liveDangerously(x?: number | null) {
  // 没有错误
  console.log(x!.toFixed());
}

// 确定赋值断言
let x!: number;
initialize();
console.log(2 * x); // 没有错误

function initialize() {
  x = 10;
}

类型守卫

// typeof 类型守卫
function padLeft2(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}'.`);
}

// 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;
  }
}

function getRandomPadder() {
  return Math.random() < 0.5
    ? new SpaceRepeatingPadder(4)
    : new StringPadder("  ");
}

let padder: Padder = getRandomPadder();

if (padder instanceof SpaceRepeatingPadder) {
  padder; // 类型细化为 'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
  padder; // 类型细化为 'StringPadder'
}

// 自定义类型守卫
function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

interface Fish {
  swim(): void;
}

interface Bird {
  fly(): void;
}

function move(pet: Fish | Bird) {
  if (isFish(pet)) {
    pet.swim(); // pet 被细化为 Fish
  } else {
    pet.fly(); // pet 被细化为 Bird
  }
}

// in 操作符类型守卫
function move2(pet: Fish | Bird) {
  if ("swim" in pet) {
    pet.swim();
  } else {
    pet.fly();
  }
}

模块和命名空间

模块

// 导出声明
export interface StringValidator {
  isAcceptable(s: string): boolean;
}

export const numberRegexp = /^[0-9]+$/;

export class ZipCodeValidator implements StringValidator {
  isAcceptable(s: string) {
    return s.length === 5 && numberRegexp.test(s);
  }
}

// 导出语句
class ZipCodeValidator2 implements StringValidator {
  isAcceptable(s: string) {
    return s.length === 5 && numberRegexp.test(s);
  }
}
export { ZipCodeValidator2 };
export { ZipCodeValidator2 as mainValidator };

// 重新导出
export { ZipCodeValidator as RegExpBasedZipCodeValidator } from "./ZipCodeValidator";

// 默认导出
export default class Calculator {
  add(x: number, y: number): number {
    return x + y;
  }
}

// 导入
import { ZipCodeValidator } from "./ZipCodeValidator";
import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
import * as validator from "./ZipCodeValidator";
import "./my-module.js";

命名空间

// 命名空间声明
namespace Validation {
  export interface StringValidator {
    isAcceptable(s: string): boolean;
  }

  const lettersRegexp = /^[A-Za-z]+$/;
  const numberRegexp = /^[0-9]+$/;

  export class LettersOnlyValidator implements StringValidator {
    isAcceptable(s: string) {
      return lettersRegexp.test(s);
    }
  }

  export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
      return s.length === 5 && numberRegexp.test(s);
    }
  }
}

// 使用命名空间
let validators: { [s: string]: Validation.StringValidator } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

高级类型技巧

索引访问类型

// 索引访问类型
type Person3 = { age: number; name: string; alive: boolean };
type Age = Person3["age"]; // number
type I1 = Person3["age" | "name"]; // string | number
type I2 = Person3[keyof Person3]; // string | number | boolean

// 数组索引访问
const MyArray = [
  { name: "Alice", age: 15 },
  { name: "Bob", age: 23 },
  { name: "Eve", age: 38 },
];

type Person4 = (typeof MyArray)[number]; // { name: string; age: number; }
type Age2 = (typeof MyArray)[number]["age"]; // number
type Age3 = Person4["age"]; // number

键值重映射进阶

// 创建布尔字段
type CreateMutable<Type> = {
  -readonly [Property in keyof Type]: Type[Property];
};

type LockedAccount = {
  readonly id: string;
  readonly name: string;
};

type UnlockedAccount = CreateMutable<LockedAccount>;
// {
//     id: string;
//     name: string;
// }

// 移除可选修饰符
type Concrete<Type> = {
  [Property in keyof Type]-?: Type[Property];
};

type MaybeUser = {
  id: string;
  name?: string;
  age?: number;
};

type User2 = Concrete<MaybeUser>;
// {
//     id: string;
//     name: string;
//     age: number;
// }

递归类型

// JSON 类型定义
type Json = string | number | boolean | null | Json[] | { [key: string]: Json };

// 树结构
interface TreeNode<T> {
  value: T;
  children?: TreeNode<T>[];
}

// 深度嵌套对象路径
type Paths<T> = T extends object
  ? {
      [K in keyof T]: K extends string
        ? T[K] extends object
          ? K | `${K}.${Paths<T[K]>}`
          : K
        : never;
    }[keyof T]
  : never;

type UserPaths = Paths<{
  name: string;
  address: {
    street: string;
    city: string;
    country: {
      name: string;
      code: string;
    };
  };
}>;
// "name" | "address" | "address.street" | "address.city" | "address.country" | "address.country.name" | "address.country.code"

协变和逆变

// 协变(Covariance)
interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

// 函数返回类型是协变的
type AnimalFunction = () => Animal;
type DogFunction = () => Dog;

// 这是允许的,因为 Dog 是 Animal 的子类型
let animalFunc: AnimalFunction = (() => ({ name: "Animal" })) as DogFunction;

// 逆变(Contravariance)
// 函数参数类型是逆变的
type AnimalHandler = (animal: Animal) => void;
type DogHandler = (dog: Dog) => void;

// 这是允许的
let dogHandler: DogHandler = ((animal: Animal) => {
  console.log(animal.name);
}) as AnimalHandler;

品牌类型

// 品牌类型(Branded Types)
type Brand<T, U> = T & { __brand: U };

type UserId = Brand<string, "UserId">;
type ProductId = Brand<string, "ProductId">;

function createUserId(id: string): UserId {
  return id as UserId;
}

function createProductId(id: string): ProductId {
  return id as ProductId;
}

function getUserById(id: UserId): User {
  // 实现获取用户逻辑
  return {} as User;
}

const userId = createUserId("user123");
const productId = createProductId("product456");

// 正确
getUserById(userId);

// 错误:类型不匹配
// getUserById(productId);

最佳实践

1. 优先使用类型推断

// 好的做法
const users = [
  { name: "Alice", age: 30 },
  { name: "Bob", age: 25 },
]; // TypeScript 自动推断类型

// 避免不必要的类型注解
const userName = users[0].name; // 自动推断为 string

2. 使用严格的类型检查

// tsconfig.json
{
    "compilerOptions": {
        "strict": true,
        "noImplicitAny": true,
        "strictNullChecks": true,
        "strictFunctionTypes": true
    }
}

3. 合理使用联合类型和类型守卫

// 好的做法:使用类型守卫
function processValue(value: string | number) {
  if (typeof value === "string") {
    return value.toUpperCase(); // TypeScript 知道这里 value 是 string
  }
  return value.toFixed(2); // TypeScript 知道这里 value 是 number
}

4. 避免使用 any

// 避免
function badFunction(data: any) {
  return data.someProperty;
}

// 更好的做法
function goodFunction<T extends { someProperty: unknown }>(data: T) {
  return data.someProperty;
}

// 或者使用 unknown
function betterFunction(data: unknown) {
  if (typeof data === "object" && data !== null && "someProperty" in data) {
    return (data as { someProperty: unknown }).someProperty;
  }
  throw new Error("Invalid data");
}

5. 使用工具类型简化代码

// 使用内置工具类型
interface User {
  id: string;
  name: string;
  email: string;
  age: number;
}

// 创建更新用户的函数
function updateUser(id: string, updates: Partial<User>) {
  // 实现更新逻辑
}

// 创建只读用户视图
type ReadonlyUser = Readonly<User>;

总结

TypeScript 的类型系统非常强大和灵活,掌握这些类型特性可以帮助你:

  1. 提高代码质量:通过静态类型检查捕获潜在错误
  2. 增强代码可读性:类型作为文档,让代码意图更清晰
  3. 改善开发体验:获得更好的 IDE 支持和自动补全
  4. 重构更安全:类型系统帮助确保重构不会破坏现有功能
  5. 团队协作:统一的类型约定提高团队开发效率

记住,TypeScript 的目标是在不牺牲 JavaScript 灵活性的前提下,提供类型安全。合理使用这些类型特性,可以让你的代码更加健壮和可维护。

四下皆无人