当前位置: 首页 > ai >正文

TypeScript 中高频出现的类型结构与用法


目录

    • 1.类型推断
    • 2.利用交叉类型和联合类型
    • 3.类型别名和接口
    • 4.keyof
    • 5.映射类型
    • 6.泛型的使用
    • 7.条件类型
    • 8.infer 关键字
    • 9.类型守卫
    • 10.非空断言操作符与可选链
    • 11.声明文件(.d.ts)
    • 12.模块化类型导入
    • 13.类型化事件
    • 14.装饰器
    • 🔹 15. const 断言(const Assertion)
    • ✅总结

TypeScript 已成为现代前端开发的标配语言。它不仅帮助我们提前发现潜在错误,还能提升代码可读性、可维护性和团队协作效率。
实际开发中,仅标注基础类型(如 string、number)无法充分发挥其类型检查能力。遇到复杂类型就放弃,最后还是靠运行时调试找问题。
你有没有遇到过这些情况?

  • 接口返回的数据结构复杂,手动写类型太麻烦
  • 函数传对象,但不知道哪些字段可选、哪些必填
  • 想复用类型,但复制粘贴了一堆重复 interface
  • 用 any 一时爽,后期维护火葬场

其实这些问题,TypeScript 早就提供了工具:keyof、Pick、Partial、infer、as const……不是 TS 不好用,是你没用对。

✅本文将梳理一些 实用技巧,帮助你深入理解核心类型模式,写出更安全、更健壮、更具可扩展性的代码。

序号主题说明
1类型推断(Type Inference)TS 自动识别类型,减少手动标注,提升开发效率
2联合类型 & 交叉类型(Union & Intersection Types)构建灵活类型的基石,`
3类型别名 vs 接口(Type Alias vs Interface)type 适合定义复杂类型别名,interface 更适合描述对象结构并支持声明合并
4keyof获取对象类型所有键的联合类型,常用于泛型约束和属性安全访问
5映射类型(Mapped Types)基于已有类型生成新类型,如 Partial<T>Pick<T, K>Readonly<T>Record<K, T>
6泛型(Generics)实现可复用的函数、类和组件的核心机制,提升代码灵活性与类型安全
7条件类型(Conditional Types)根据类型关系进行判断选择,语法为 T extends U ? X : Y,用于构建高级类型逻辑
8infer 关键字在条件类型中“推断”出子类型,常用于提取数组元素、函数返回值、Promise 解包等场景
9类型守卫(Type Guards)在运行时缩小联合类型范围,包括 typeofinstanceofin 和自定义守卫函数
10非空断言(!)与可选链(?.)! 告诉 TS 值不为 null/undefined;?. 安全访问嵌套属性,避免运行时错误
11声明文件(.d.ts)为 JavaScript 库或全局变量提供类型定义,提升第三方代码的类型安全性
12模块化类型导入(import type)使用 import type 导入仅用于类型的模块,避免运行时引入,优化打包体积
13类型化事件(Typed Event)为事件系统添加参数类型约束,实现类型安全的发布-订阅模式
14装饰器(Decorators)使用 @decorator 修饰类、方法、属性,常见于 Angular、NestJS 等框架中
15const 断言(as const将字面量对象或数组标记为完全只读和最小化类型,保留字面量类型(如 'dark' 而非 string),适用于配置、常量和模拟枚举

1.类型推断

类型推断(Type Inference)是 TypeScript 的一个强大的特性。它允许编译器根据上下文自动推断出变量的类型,从而减少手动输入类型的工作量,同时也提高了代码的可维护性和可读性。

✅优势:减少冗余代码,提升开发效率, 示例:

let num = 5;        // 推断为 number
let str = "hello";  // 推断为 string
let bool = true;    // 推断为 booleanfunction add(a: number, b: number) {return a + b; // 推断返回值为 number
}let result = add(num, 10);

2.利用交叉类型和联合类型

交叉类型(Intersection Types)允许将多个类型合并为一个类型,新类型将具有所有类型的特性。我们可以使用符号 & 运算符将两个或多个类型组合成一个交叉类型。
联合类型(Union Types)表示一个值可以有多种类型之一。我们可以使用符号 | 运算符将两个或多个类型组合成一个联合类型。

✅交叉类型(&)
将多个类型合并为一个新类型,拥有所有成员。

interface Dog { walk(): void }
interface Cat { meow(): void }type Pet = Dog & Cat;const myPet: Pet = {walk() { console.log('walking') },meow() { console.log('meowing') }
}

✅ 应用场景:联合类型用于多态输入,交叉类型用于 mixin 或组合对象。

✅联合类型(|)
联合类型的用法就是使用 | 运算符将多个类型组合在一起,表示一个值可以是多种类型之一。

interface Square {side: number;
}interface Circle {radius: number;
}function calculateArea(shape: Square | Circle) {if ('side' in shape) {return shape.side ** 2;} else {return Math.PI * shape.radius ** 2;}
}
type Color = 'red' | 'green' | 'blue';
type ID = string | number;function printId(id: ID) {console.log(id.toUpperCase()); // ❌ error: number 没有 toUpperCase
}

3.类型别名和接口

1.类型别名
类型别名(Type Aliases)是一种给一个已经存在的类型起一个新的名字的方式。通过 type 关键字可以定义一个类型别名,支持原始类型、联合、交叉等。

type MyString = string;
type MyNumber = number;type Person = {name: string;
age: number;
};
type Person = {name: string;age: number;
};type Status = 'active' | 'inactive';

类型别名可以很方便地给复杂的类型定义一个简洁的名称,从而提高代码可读性,并且还可以使用联合类型、交叉类型等高级类型

type Color = 'red' | 'green' | 'blue';
type Shape = { kind: 'circle'; radius: number } | { kind: 'square'; length: number };function draw(shape: Shape, color: Color) {// ...
}

2.接口
接口(Interfaces)是一种描述对象结构的方式,在 TypeScript 中通过 interface 关键字来定义。接口可以包含属性、方法和索引签名等

interface Person {name: string;age: number;sayHello: () => void;
}let person: Person = {name: 'Alice',age: 30,sayHello() {console.log(`Hello, my name is ${this.name}.`);},
};

接口在描述对象结构时非常有用,它可以提供更好的代码组织性和可读性,并且也可以在一些特定场景下提供更好的类型安全性。另外需要注意的是,接口只能描述对象的形状,不能描述具体的实现方式。如果需要描述具体的实现方式,可以使用类或函数类型。

4.keyof

keyof 是 TypeScript 中的一个关键字,用于获取对象类型的所有键的联合类型。它可以帮助我们在编写泛型函数或操作对象属性时,提供更好的类型安全性,常用于泛型约束。

✅ 作用:实现类型安全的属性访问。

interface Person {name: string;age: number;gender: 'male' | 'female';
}function getProperty<T, K extends keyof T>(obj: T, key: K) {return obj[key];
}let person: Person = { name: 'Alice', age: 30, gender: 'female' };
let name = getProperty(person, 'name');
let age = getProperty(person, 'age');
let gender = getProperty(person, 'gender');

5.映射类型

TypeScript 中的映射类型(Mapped Types)是一种非常强大的类型操作符,它可以根据一个已有的对象类型,生成一个新的对象类型。映射类型可以帮助我们进行一些常见的类型转换和操作,如将所有属性变成可选属性、添加或删除属性、修改属性类型等等。
TypeScript 中的映射类型有以下四种:

  1. Partial:将类型 T 中所有属性变为可选属性。
interface Person {name: string;age: number;gender: 'male' | 'female';
}type PartialPerson = Partial<Person>;// 等价于
// interface PartialPerson {
//   name?: string;
//   age?: number;
//   gender?: 'male' | 'female';
// }
  1. Readonly:将类型 T 中所有属性变为只读属性。
interface Person {name: string;age: number;gender: 'male' | 'female';
}type ReadonlyPerson = Readonly<Person>;// 等价于
// interface ReadonlyPerson {
//   readonly name: string;
//   readonly age: number;
//   readonly gender: 'male' | 'female';
// }
  1. Record<K, T>:创建一个新的对象类型,其属性名类型为 K,属性值类型为 T
type Dictionary<T> = Record<string, T>;let dict: Dictionary<number> = {foo: 123,bar: 456,
};
  1. Pick<T, K>:从类型 T 中选择指定的属性 K,并返回一个新的对象类型。
interface Person {name: string;age: number;gender: 'male' | 'female';
}type PersonNameAndAge = Pick<Person, 'name' | 'age'>;// 等价于
// interface PersonNameAndAge {
//   name: string;
//   age: number;
// }

还有一种映射类型叫做 Keyof,它用于获取一个对象类型中所有属性名组成的联合类型。这个类型在前面的问题中已经讲到过了,这里就不再赘述。

6.泛型的使用

泛型可以让我们编写更具灵活性、可重用性和类型安全性的代码。在 TypeScript 中,泛型通常使用类型参数来定义一个通用的类型或函数,并在使用时指定具体的类型。
我们想编写一个函数来反转任意数组,假设我们不使用泛型,代码可能会是这样:

function reverseStrings(items: string[]): string[] {return items.reverse();
}function reverseNumbers(items: number[]): number[] {return items.reverse();
}

但是这种方法显然不够优雅,因为我们需要分别编写两个函数来处理 string 和 number 类型的数组,并且当我们需要处理其他类型的数组时,我们必须再次编写新的函数。
使用泛型,我们可以很容易地创建一个通用的函数来处理任何类型的数组:

function reverse<T>(items: T[]): T[] {return items.reverse();
}const words = ['hello', 'world'];
const reversedWords = reverse<string>(words); 
console.log(reversedWords); // ['world', 'hello']const numbers = [1, 2, 3];
const reversedNumbers = reverse<number>(numbers);
console.log(reversedNumbers); // [3, 2, 1]

✅ 核心思想:用类型参数 T 实现“一次编写,多处使用”。

7.条件类型

条件类型(Conditional Types)允许我们根据类型之间的关系来选择不同的类型。它的语法类似于三元运算符:T extends U ? X : Y,表示如果 T 可以赋值给 U,则结果为 X,否则为 Y。

这在处理复杂类型逻辑时非常有用,比如过滤类型、提取结构等。
✅ 使用场景

// 判断是否为字符串类型
type IsString<T> = T extends string ? true : false;type A = IsString<'hello'>;  // true
type B = IsString<123>;      // false// 过滤联合类型中的某些成员
type FilterString<T> = T extends string ? T : never;
type OnlyStrings = FilterString<'a' | 'b' | 1 | 2>; // 'a' | 'b'

✅ 内置工具类型
TypeScript 内置了很多基于条件类型的工具:

type MyExclude<T, U> = T extends U ? never : T;
type MyExtract<T, U> = T extends U ? T : never;
type MyNonNullable<T> = T extends null | undefined ? never : T;

这些是 Exclude<T, U>、Extract<T, U>、NonNullable 的底层实现原理。

8.infer 关键字

infer 是 “infer”(推断)的缩写, 在条件类型中推断类型,用于在 extends 子句中声明一个待推断的类型变量。它常与条件类型配合使用,用来“提取”复杂类型中的子类型。

✅ 使用场景

// 提取数组元素类型
type ElementType<T> = T extends (infer U)[] ? U : T;
type A = ElementType<number[]>;  // number
type B = ElementType<string>;    // string// 提取函数返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : T;
type Fn = () => string;
type R = ReturnType<Fn>;  // string// 提取 Promise 解包后的类型
type Unpacked<T> = T extends Promise<infer U> ? U : T;
type Data = Unpacked<Promise<string>>;  // string

✅ 实际应用:当你调用一个返回 Promise 的 API 时,可以用 infer 自动解析出 T,避免手动声明。

9.类型守卫

类型守卫(Type Guards)是 TypeScript 中用来检测类型的一种机制,它可以帮助开发者在运行时检测某个变量的类型,并在不同的条件下提供不同的类型声明。
在 TypeScript 中,有四种常见的类型守卫方式:

  1. typeof 类型守卫
function foo(x: number | string) {if (typeof x === 'number') {// x is number} else {// x is string}
}
  1. instanceof 类型守卫
class MyClass {}function foo(x: any) {if (x instanceof MyClass) {// x is an instance of MyClass}
}
  1. 自定义类型守卫函数
interface A { a: number }
interface B { b: number }function isA(x: any): x is A {return typeof x.a === 'number';
}function foo(x: A | B) {if (isA(x)) {// x is an instance of A} else {// x is an instance of B}
}
  1. in 操作符类型守卫
interface A { a: number }
interface B { b: number }function foo(x: A | B) {if ('a' in x) {// x is an instance of A} else {// x is an instance of B}
}

10.非空断言操作符与可选链

非空断言操作符(!)与可选链(?.)这两个特性极大提升了处理可能为 null 或 undefined 值的安全性和简洁性。

✅ 非空断言操作符 !
告诉 TypeScript 编译器:“我确定这个值不是 null 或 undefined”。

const el = document.getElementById('app')!;
el.innerHTML = 'Hello World'; // 不会报错,即使 getElementById 返回可能为 null

⚠️ 注意:使用 ! 要谨慎,确保运行时确实存在,否则会导致 JS 运行时错误。

✅ 可选链操作符 ?.
安全地访问嵌套对象属性,避免 Cannot read property ‘x’ of undefined 错误。

interface User {profile?: {address?: {city?: string;};};
}const user: User = {};// 安全访问
const city = user.profile?.address?.city; // string | undefined// 也可用于函数调用
user.logout?.(); // 如果 logout 存在就调用

✅ 推荐组合使用

结合空值合并 ??,实现默认值 fallback。

const name = users[0]?.name ?? 'Unknown';

11.声明文件(.d.ts)

声明文件(Declaration File)是一种特殊的类型文件,用来描述外部 JavaScript 库、模块或对象的类型,以便在 TypeScript 代码中正确引用和使用它们。
TypeScript 编译器可以根据 JavaScript 库的源代码推断出其类型信息,但某些 JavaScript 库并没有提供类型定义文件,或者类型定义文件不完整或不准确,这时我们需要手动编写声明文件。声明文件的扩展名为 .d.ts,可以与 TypeScript 文件一起放置在项目目录中。声明文件的编写方式有以下几种:

  1. 定义全局变量和函数
    如果我们需要在 TypeScript 代码中调用浏览器原生 API 或其他 JavaScript 库中的全局变量和函数,就需要手动编写声明文件来告诉 TypeScript 对应变量和函数的类型。例如:
// global.d.ts
declare const $: (selector: string) => any;$('#my-element').addClass('active');
  1. 扩展已有类型
    有时候我们需要扩展已有的类型定义,以适应自己的需求,这时可以使用 interface、namespace 等关键字来定义和扩展类型。例如:
interface String {reverse(): string;
}const str = 'Hello, world!';
console.log(str.reverse()); // "!dlrow ,olleH"
  1. 模块声明
    如果我们要使用一个已有的 JavaScript 模块,但模块本身没有提供类型定义文件,或者类型定义文件不完整或不准确,这时我们需要手动编写声明文件来告诉 TypeScript 模块的类型信息。例如:
declare module 'my-lib' {export function greet(name: string): string;
}

12.模块化类型导入

在大型项目中,合理组织类型文件至关重要。TypeScript 支持 ES Module 和 namespace 两种方式,推荐使用模块化方式。
✅ 模块化类型导出(推荐)

// types/user.ts
export interface User {id: number;name: string;email?: string;
}export type Role = 'admin' | 'user' | 'guest';
// main.ts
import type { User, Role } from './types/user'; // 使用 import type 只导入类型,不生成 JS 代码const currentUser: User = { id: 1, name: 'Alice' };

✅ import type 是编译期导入,编译后不生成代码,提升性能;确保类型不会出现在运行时代码中,提升性能;支持类型拆分,便于维护。

13.类型化事件

类型化事件(Typed Event)是一种可以指定事件处理函数接收参数类型、返回值类型的事件机制。通过使用类型化事件,我们可以在编译时对事件处理函数的类型进行检查,以避免运行时因类型不匹配而导致的错误。
✅示例,如何定义和使用类型化事件:

interface EventHandler<T> {(args: T): void;
}class TypedEvent<T> {private handlers: EventHandler<T>[] = [];public addHandler(handler: EventHandler<T>) {this.handlers.push(handler);}public removeHandler(handler: EventHandler<T>) {const index = this.handlers.indexOf(handler);if (index >= 0) {this.handlers.splice(index, 1);}}public raise(args: T) {for (const handler of this.handlers) {handler(args);}}
}// 定义一个事件参数类型
interface LoginEventArgs  {message: string;
}// 创建一个类型化事件实例
const onLogin  = new TypedEvent<LoginEventArgs >();// 添加一个事件处理函数
myEvent.addHandler((args: MyEventArgs) => {console.log(args.message); // 类型安全
});// 触发事件
onLogin  .raise({ message: 'Hello, world!' });

14.装饰器

装饰器是一种特殊的语法,它可以用来修饰类、方法、属性以及参数等元素,从而达到一些特定的目的。在 TypeScript 中,我们可以使用 @ 符号来声明一个装饰器

function log(target: any, key: string, descriptor: PropertyDescriptor) {const original = descriptor.value;descriptor.value = function(...args: any[]) {console.log(`Calling ${key} with`, args);return original.apply(this, args);};return descriptor;
}class UserService {@loglogin(username: string) {console.log(`User ${username} logged in`);}
}new UserService().login('Alice');
// 输出:
// Calling login with ["Alice"]
// User Alice logged in

✅ TypeScript 中的装饰器可以用于很多场景,例如实现依赖注入、自动绑定事件、路由映射等等。常见的装饰器包括 @Injectable、@Component、@ViewChild、@RouterConfig 等等。

🔹 15. const 断言(const Assertion)

const 断言是 TypeScript 中一种强大的字面量类型控制方式,它能帮助我们最小化类型推断的“宽松性”,让字面量对象、数组等保持最具体的类型,避免不必要的类型扩展。

它使用 as const 语法,告诉 TypeScript:“请把这个值当作完全不可变的字面量来处理”。

✅ 为什么需要 const 断言?
默认情况下,TypeScript 会对对象和数组进行“宽松推断”,可能导致类型不够精确。
示例:没有 const 断言的问题

const config = {mode: 'dark',timeout: 3000,enabled: true,tags: ['react', 'ts']
};// config 的类型实际上是:
// {
//   mode: string;
//   timeout: number;
//   enabled: boolean;
//   tags: string[];
// }// 如果你希望 mode 只能是 'dark' | 'light',这里已经丢失了字面量信息!

此时 config.mode 的类型是 string,而不是 ‘dark’,这会导致类型检查失效。

✅ 使用 const 断言解决

const config = {mode: 'dark',timeout: 3000,enabled: true,tags: ['react', 'ts']
} as const;// 现在 config 的类型是:
// readonly {
//   readonly mode: "dark";
//   readonly timeout: 3000;
//   readonly enabled: true;
//   readonly tags: readonly ["react", "ts"];
// }

mode 的类型是字面量 ‘dark’(而不是 string)
tags 是 readonly [“react”, “ts”],长度和内容都被固定
所有属性和数组都变为 readonly,防止意外修改

✅ 实际应用场景

  1. 定义常量配置(推荐)
const ENV = {API_URL: 'https://api.example.com',VERSION: '1.0.0',FEATURES: {darkMode: true,analytics: false}
} as const;// 后续使用时,TypeScript 知道 ENV.API_URL 就是字符串字面量
// 可用于联合类型匹配、环境判断等
  1. 联合类型中的字面量集合
const COLORS = ['red', 'green', 'blue'] as const;
type Color = typeof COLORS[number]; // 'red' | 'green' | 'blue'function paint(color: Color) {console.log(`Painting with ${color}`);
}paint('red');  // ✅ OK
paint('pink'); // ❌ Error: not in union
  1. 模拟枚举(无编译输出)
const Direction = {Up: 'UP',Down: 'DOWN',Left: 'LEFT',Right: 'RIGHT'
} as const;type Direction = typeof Direction[keyof typeof Direction];
// 等价于: 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'

✅ 优势:没有编译后的 JS 输出(不像 enum),更轻量。
as const 是“类型最小化”的利器,它让字面量保持“原形”,避免 TypeScript 过度放宽类型,是构建精确类型系统的关键一环。

✅总结

📌 TypeScript 的目标不是“写更多类型”,而是“写更少 bug”。

TypeScript 不只是一个“加类型”的工具,它是一套完整的类型系统,能帮助我们写出更安全、可维护、可复用的代码。掌握这些核心技巧,不仅能提升开发效率,还能在团队协作中减少 bug、提升代码质量。建议从基础开始,逐步深入泛型、条件类型等高级特性,最终在项目中形成自己的“类型设计模式”。

http://www.xdnf.cn/news/17171.html

相关文章:

  • C++模板知识点6『拆分模板参数』
  • 任务进度状态同步 万能版 参考 工厂+策略+观察者设计模式 +锁设计 springboot+redission
  • C++ 类和对象(2)
  • 顺序表——C语言
  • C++之队列浅析
  • SpringBoot学习日记 Day5:解锁企业级开发核心技能
  • 亚马逊采购风控突围:构建深度隐匿的环境安全体系
  • 剧本杀小程序系统开发:推动社交娱乐产业创新发展
  • TikTok Shop冷启动破局战:亚矩阵云手机打造爆款账号矩阵
  • 项目构想|文生图小程序
  • 人工智能2.0时代的人才培养和通识教育
  • 动手学深度学习(pytorch版):第一节——引言
  • Redis学习总结(持续更新)
  • 【45】C++函数重载是什么?函数重载需要注意什么?为什么C++支持函数重载,C语言不支持函数重载?C++和C语言代码之间如何相互调用?
  • 仓库管理系统-20-前端之记录管理的联表查询
  • 2025最新国内服务器可用docker源仓库地址大全(2025年8月更新)
  • 深入剖析Java线程:从基础到实战(上)
  • 上海一家机器人IPO核心零部件依赖外购, 募投计划频繁修改引疑
  • AI绘画:生成唐初李世民全身像提示词
  • idea工具maven下载报错:PKIX path building failed,配置忽略SSL检查
  • 打造交互界面 —— Popup 的艺术
  • 使用萤石云播放视频及主题模版配置
  • 设计模式 观察者模式
  • 软件测试中,pytest 的 yield 有什么作用?
  • Day32--动态规划--509. 斐波那契数,70. 爬楼梯,746. 使用最小花费爬楼梯
  • 第一个vue应用
  • 【性能测试】---测试工具篇
  • JavaSE---异常的经典面试题
  • Git `cherry-pick` 工具汇总
  • 数组指针-函数指针-回调函数