TypeScript 交叉类型与模板字面量类型深度解析(二十二)

在现代前端开发中,TypeScript 的强大类型系统为我们提供了更多的安全保障和开发便利。本文将深入探讨 TypeScript 中的 交叉类型模板字面量类型,帮助你更好地理解和应用这些高级类型特性。

什么是交叉类型?

交叉类型(Intersection Types)是 TypeScript 中的一种类型组合方式,它允许我们将多个类型合并成一个新的类型。新类型将包含所有被合并类型的所有成员。这类似于面向对象编程中的多重继承,使得一个类型可以拥有多个类型的特性。

为什么需要交叉类型?

在实际开发中,一个对象往往需要具备多种特性。例如,一个员工既是一个人(Person),也是一个工作者(Worker),需要同时具有两者的属性。交叉类型让我们可以将多个类型合并成一个,满足这种需求。

基本语法

使用 & 符号可以组合多个类型。例如:

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

type Worker = {
  company: string;
  salary: number;
};

type Employee = Person & Worker;

在这个例子中,Employee 类型包含了 Person 和 Worker 的所有属性。

实例

const employee: Employee = {
  name: "Alice",
  age: 25,
  company: "Google",
  salary: 100000
};

console.log("员工: " + JSON.stringify(employee));

运行结果:

员工: {"name":"Alice","age":25,"company":"Google","salary":100000}

交叉类型与接口继承

交叉类型可以替代接口的多重继承,提供更简洁的类型组合方式。

接口继承

interface A {
  a: string;
}

interface B {
  b: number;
}

interface AB extends A, B {
  c: boolean;
}

const obj: AB = {
  a: "hello",
  b: 42,
  c: true
};

console.log("对象: " + JSON.stringify(obj));

交叉类型

type ABType = A & B & {
  c: boolean;
};

const obj: ABType = {
  a: "hello",
  b: 42,
  c: true
};

console.log("对象: " + JSON.stringify(obj));

运行结果:

对象: {"a":"hello","b":42,"c":true}

类型混合(Mixin 模式)

使用交叉类型可以实现 Mixin 模式,动态组合类的功能,而无需修改原有类的结构。

定义构造函数类型

type Constructor = new (...args: any[]) => {};

Mixin:添加时间戳功能

function Timestamped<T extends Constructor>(Base: T) {
  return class extends Base {
    timestamp = Date.now();
  };
}

Mixin:添加序列化功能

function Serializable<T extends Constructor>(Base: T) {
  return class extends Base {
    serialize() {
      return JSON.stringify(this);
    }
  };
}

基础用户类

class User {
  name: string;

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

组合 Mixin

const TimestampedUser = Timestamped(User);
const SerializableUser = Serializable(User);
const FullUser = Serializable(Timestamped(User));

const user = new FullUser("Alice");
console.log("时间戳: " + user.timestamp);
console.log("序列化: " + user.serialize());

运行结果:

时间戳: 17134...
序列化: {"name":"Alice","timestamp":17134...}

交叉类型与联合类型

交叉类型和联合类型结合时需要注意优先级问题。联合类型的优先级高于交叉类型。

实例

type StringOrNumber = string | number;
type Both = string & number; // never,因为没有类型同时是字符串和数字

type A = { a: string };
type B = { b: number };
type C = { c: boolean };

type Combined = (A | B) & C; // { a: string; c: boolean } | { b: number; c: boolean }

const obj: Combined = {
  a: "hello",
  c: true
};

console.log("组合: " + JSON.stringify(obj));

运行结果:

组合: {"a":"hello","c":true}

实用交叉类型

交叉类型常用于创建工具类型,实现类型的转换。

映射类型:将所有属性变为可选

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

映射类型:将所有属性变为必填


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

映射类型:将所有属性变为只读

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

定义配置接口

interface Config {
  host: string;
  port: number;
}

使用工具类型

const partialConfig: Partial
<Config> = {
  host: "localhost"
};

const requiredConfig: Required
<Config> = {
  host: "localhost",
  port: 8080
};

const readonlyConfig: Readonly
<Config> = {
  host: "localhost",
  port: 8080
};

console.log("部分: " + JSON.stringify(partialConfig));
console.log("必填: " + JSON.stringify(requiredConfig));
console.log("只读: " + JSON.stringify(readonlyConfig));

运行结果:

部分: {"host":"localhost"}
必填: {"host":"localhost","port":8080}
只读: {"host":"localhost","port":8080}

注意事项

  • 不兼容类型: 交叉不兼容的类型会得到 never。
  • 优先级: 联合类型的优先级高于交叉类型。
  • 方法冲突: 如果两个类型有同名的方法,需要手动处理冲突。

总结

交叉类型是 TypeScript 中强大的类型组合工具,可以帮助我们创建灵活的类型组合。合理使用交叉类型可以提高代码的类型安全性,减少运行时错误。

什么是模板字面量类型?

模板字面量类型(Template Literal Types)是 TypeScript 中的一种类型系统特性,允许我们基于字符串字面量类型构建新的类型。通过插值语法,我们可以生成更加精确的字符串类型,适用于事件名、API 路径、CSS 类名等场景。

为什么需要模板字面量类型?

在开发中,我们经常需要处理格式化的字符串,如事件名(onClick)、API 路径(get:/users)、CSS 类名(btn-primary-md)等。使用普通的 string 类型无法精确描述这些格式,而模板字面量类型让我们可以精确地定义这些字符串的类型,从而增强 TypeScript 的类型安全性,减少运行时错误。

基本语法

模板字面量类型使用反引号( )和插值语法(${})来定义类型。例如:

`typescript type Event = "click" | "focus"; type Method = "get" | "post";

type EventName = on${Capitalize<Event>}; type ApiPath = ${Method}:/users; `

实例

`typescript const eventName: EventName = "onClick"; const apiPath: ApiPath = "get:/users";

console.log("事件: " + eventName); console.log("路径: " + apiPath); `

运行结果:

事件: onClick 路径: get:/users

内置工具类型

TypeScript 提供了四个内置的工具类型来处理字符串大小写:

  • Uppercase:将字符串转为大写
  • Lowercase:将字符串转为小写
  • Capitalize:将字符串首字母大写
  • Uncapitalize:将字符串首字母小写

实例

`typescript type UpperHello = Uppercase<"hello">; // "HELLO" type LowerHELLO = Lowercase<"HELLO">; // "hello" type CapitalizedHello = Capitalize<"hello">; // "Hello" type UncapitalizedHello = Uncapitalize<"Hello">; // "hello"

console.log("Uppercase: " + UpperHello); console.log("Lowercase: " + LowerHELLO); console.log("Capitalize: " + CapitalizedHello); console.log("Uncapitalize: " + UncapitalizedHello); `

运行结果:

Uppercase: HELLO Lowercase: hello Capitalize: Hello Uncapitalize: hello

事件类型

使用模板字面量类型可以精确地定义事件名称的类型,确保事件名的格式正确。

实例

`typescript type EventName = on${Capitalize<string>}; type Handler = handle${Capitalize<string>};

const clickEvent: EventName = "onClick"; const focusEvent: EventName = "onFocus"; const handler: Handler = "handleSubmit";

console.log("事件: " + clickEvent); console.log("处理器: " + handler); `

运行结果:

事件: onClick 处理器: handleSubmit

路径类型

使用模板字面量类型可以精确地定义 API 路径的类型,确保路径的格式正确。

实例

`typescript type HttpMethod = "get" | "post" | "put" | "delete"; type ApiEndpoint = /${string};

type ApiPath = ${HttpMethod}${ApiEndpoint};

const getUsers: ApiPath = "/get/users"; const createUser: ApiPath = "/post/users";

console.log("路径: " + getUsers); console.log("路径: " + createUser); `

运行结果:

路径: /get/users 路径: /post/users

复杂示例

模板字面量类型可以组合多个联合类型,生成所有可能的组合。

实例

`typescript type Row = row${number}; type Row10 = Row; // row0, row1, row2... 直到 row9...

type Variant = "primary" | "secondary"; type Size = "sm" | "md" | "lg";

type ClassName = btn-${Variant}-${Size};

const className: ClassName = "btn-primary-md";

console.log("类名: " + className); `

运行结果:

类名: btn-primary-md

自定义工具类型

可以创建自己的模板字面量工具类型,以满足特定的需求。

实例

`typescript type Prefix<T extends string, P extends string> = ${P}${Capitalize<T>}; type Suffix<T extends string, S extends string> = ${Capitalize<T>}${S};

type HandlerName = Prefix<"click", "on">; type ButtonId = Suffix<"submit", "Btn">;

const handler: HandlerName = "onClick"; const buttonId: ButtonId = "SubmitBtn";

console.log("处理器: " + handler); console.log("按钮ID: " + buttonId); `

运行结果:

` 处理器: onClick 按钮ID: SubmitBtn ``

总结

模板字面量类型是 TypeScript 中一种强大的类型系统特性,可以帮助我们精确地定义字符串类型的格式。合理使用模板字面量类型可以提高代码的类型安全性,减少运行时错误。

通过本文的介绍,希望你能够更好地理解和应用 TypeScript 中的交叉类型和模板字面量类型,提升你的开发效率和代码质量。