TypeScript 更安全的 6 个写法:别再用 any 了

更新日期: 2026-04-28 阅读: 45 标签: TypeScript

TypeScript 很强大,但大多数开发者只用到了皮毛。遇到报错就甩一个 any,用 as 让编译器闭嘴,然后纳闷为什么运行时还是出错。

下面这 6 个写法,能让你的 TypeScript 代码更安全,在 bug 跑到用户面前之前就抓住它。


1. 用 unknown 代替 any

把某个值标成 any,等于告诉 TypeScript:“别管我,什么都别检查。”这跟不用 TypeScript 有什么区别?

unknown 是 any 的安全版本。你可以把任何值赋给它,但 TypeScript 不允许你对它做任何操作,除非你先做类型判断。

any 的问题

let data: any = fetchSomething();
data.toFixed(2);      // 没报错,但 data 要是字符串呢?
data.toUpperCase();   // 没报错,但 data 要是数字呢?
console.log(data.foo.bar.baz); // 运行直接炸

用 unknown 改正

let data: unknown = fetchSomething();
// data.toFixed(2);  ← 报错:Object is of type 'unknown'

if (typeof data === "string") {
  console.log(data.toUpperCase()); // 安全
}

if (typeof data === "number") {
  console.log(data.toFixed(2));    // 安全
}

什么时候用 unknown:API 响应、用户输入、URL 参数、JSON.parse() 的结果、catch 里的错误对象。


2. 用 satisfies 代替 as

as 是类型断言,你告诉 TypeScript“相信我,我比你懂”。问题是 TypeScript 真信你,哪怕你是错的。

satisfies 是 TypeScript 4.9 引入的。它验证值是否符合某个类型,同时保留更精确的推断类型。

as 的问题

type Color = "red" | "green" | "blue";

type Theme = {
  primary: Color;
  secondary: Color;
  surface: string;
};

const broken = {
  primary: "red",
  secondary: "grn",   // 拼写错了,但没报错
  surface: "#fff"
} as Theme;

用 satisfies 改正

const safe = {
  primary: "red",
  secondary: "grn",   // 报错:'"grn"' is not assignable to type 'Color'
  surface: "#fff"
} satisfies Theme;

satisfies 还有个好处:保留字面量类型。上面例子中 theme.primary 的类型是 "red",不是 Color 联合类型,后续代码能得到更精确的自动补全。

什么时候可以用 as:操作 DOM 时,比如 document.getElementById('x') as HTMLInputElement。其他情况优先用 satisfies。


3. 用 is 写自定义类型守卫

typeof 和 instanceof 不够用的时候,可以用 is 自己写类型判断函数。

type Species = "cat" | "dog";

interface Pet {
  species: Species;
  name: string;
}

class Cat implements Pet {
  species: Species = "cat";
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  purr(): void {
    console.log(`${this.name} purrs softly...`);
  }
}

class Dog implements Pet {
  species: Species = "dog";
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  fetch(): void {
    console.log(`${this.name} fetches the ball!`);
  }
}

function isCat(pet: Pet): pet is Cat {
  return pet.species === "cat";
}

function isDog(pet: Pet): pet is Dog {
  return pet.species === "dog";
}

function interact(pet: Pet) {
  if (isCat(pet)) {
    pet.purr();   // TypeScript 知道是 Cat
  } else if (isDog(pet)) {
    pet.fetch();  // TypeScript 知道是 Dog
  }
}

实际应用:校验 API 响应、处理可辨识联合类型、区分不同的错误类型。

小技巧:类型守卫和数组的 filter 配合很好用:const cats = pets.filter(isCat),TypeScript 会自动推断出 Cat[] 类型。


4. 用联合类型代替 Enum

TypeScript 的 enum 看起来方便,但有代价:会生成额外的 JavaScript 代码,数值枚举容易出问题,对 tree-shaking 不友好。

enum 的问题

enum Status {
  Pending,    // 0
  Active,     // 1
  Suspended,  // 2
}

const s: Status = 99;  // 没报错,但 99 根本不是有效的 Status

用联合类型改正

type Status = "pending" | "active" | "suspended";

function setStatus(status: Status) {
  console.log(`Setting status: ${status}`);
}

setStatus("active");    // 正常
setStatus("invalid");   // 报错

如果需要运行时对象来遍历或反向查找,可以用 as const:

const STATUS = {
  Pending: "pending",
  Active: "active",
  Suspended: "suspended",
} as const;

type Status = (typeof STATUS)[keyof typeof STATUS];
// type Status = "pending" | "active" | "suspended"

Object.values(STATUS).forEach(s => console.log(s));


5. 用 Record 标注对象类型

别用 Object 或 {} 做类型。Object 什么都能接,{} 表示“任何非空值”,都不是你想要的。

// 错误示范
const config: Object = new Date();  // 没报错
const what: {} = "hello";           // 没报错

// 正确写法
type Config = Record<string, unknown>;

const config: Config = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
  debug: true,
};

Record 在约束键的场景更好用:

type Role = "admin" | "editor" | "viewer";
type Permissions = Record<Role, string[]>;

const perms: Permissions = {
  admin: ["read", "write", "delete"],
  editor: ["read", "write"],
  viewer: ["read"],
  // 少一个键会报错
};


6. 用模板字面量类型组合字符串

TypeScript 的模板字面量类型可以动态构造字符串类型。把两个联合类型放一起,TypeScript 自动生成所有可能的组合。

type Size = "sm" | "md" | "lg";
type Color = "primary" | "secondary" | "danger";

type ButtonVariant = `${Size}-${Color}`;
// 结果是 "sm-primary" | "sm-secondary" | "sm-danger" | "md-primary" | ...

const btn: ButtonVariant = "md-primary";   // 正常
const bad: ButtonVariant = "xl-primary";   // 报错

实际应用

// CSS 工具类
type Spacing = 0 | 1 | 2 | 4 | 8;
type Direction = "t" | "r" | "b" | "l" | "x" | "y";
type MarginClass = `m${Direction}-${Spacing}`;

// 事件处理函数名
type DomEvent = "click" | "focus" | "blur";
type HandlerName = `on${Capitalize<DomEvent>}`;
// 结果是 "onClick" | "onFocus" | "onBlur"

TypeScript 提供了 Capitalize、Uncapitalize、Uppercase、Lowercase 这些工具类型,可以在模板字面量里用。


速查表

要做什么避免用推荐用
未知类型anyunknown
类型验证assatisfies
类型收窄手动判断is 类型守卫
常量集合enum联合类型或 as const
对象类型Object 或 {}Record<K, V>
字符串组合手动列举模板字面量类型

好的 TypeScript 代码不在于写更多类型,而在于写对的类型。每个模式做一件事:让编译器替你抓 bug,这样用户就不用撞上它们了。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://fly63.com/article/detial/13730

相关推荐

Nerv_一款类 React 前端框架,基于虚拟 DOM 技术的 JavaScript(TypeScript) 库

Nerv_是一款由京东凹凸实验室打造的类 React 前端框架,基于虚拟 DOM 技术的 JavaScript(TypeScript) 库。它基于React标准,提供了与 React 16 一致的使用方式与 API。

使用TypeScript两年后-值得吗?

差不多两年前,我在一个创业团队中开始了一个全新的项目。用到的全都是类似Microservices,docker,react,redux这些时髦的东西。我在前端技术方面积累了一些类似的经验

TypeScript最佳实践:是否使用noImplicitAny

我应该使用noImplicitAny TypeScript编译器标志吗?noImplicitAny编译器选项所做的,基本上是将TypeScript从可选类型语言转换为强制类型检验语言。这使得TypeScript离JavaScript的超集稍微远了一些,因为简单的:

为什么要学习Typescript 语言呢?Typescript 开发环境安装

TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集,TypeScript是JavaScript类型的超集,它可以编译成纯JavaScript。TypeScript可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的。

5分钟了解TypeScript

有两种方式安装TypeScript,如何创建第一个TypeScript文件,在TypeScript中,可以使用interface来描述一个对象有firstName和lastName两个属性,TypeScript支持JavaScript的新功能,其中很重要的一个功能就是基于类的面向对象编程

Typescript中以变量方式传递类

最近尝试用TypeScript写一个工具库,需要实现这样一个场景:声明一个抽象类Parent,声明一组子类ChildA、ChildB继承这个Parent,实现它的抽象方法

TypeScript_TS系列之高级类型

交叉类型:将多个类型合并为一个类型、联合类型:表示取值可以为多种类型中的一种、混合类型:一个例子就是,一个对象可以同时做为函数和对象使用,并带有额外的属性、类型断言:可以用来手动指定一个值的类型

用TypeScript弥补Elm和JavaScript之间的差距

近些日子,我使用了新语言编程,从JavaScript,切确地说是Elm,转成TypeScript。在本文中,我将继续深挖一些我非常喜欢的TypeScript特性。

TypeScript_命名空间(namespace)

什么时候要用命名空间?如果你发现自己写的功能(函数/类/接口等...)越来越多, 你想对他们进行分组管理就可以用命名空间, 下面先用类,举例:发现namespace下还有export, export在这里用来表示哪些功能是可以外部访问的:

TypeScript功能:const断言

我发现官方的 TypeScript 文档非常有用,但是总觉得有点过于学术化并且枯燥无味。每当我发现一个新功能时,我想要知道这个功能究竟能够解决什么问题而不是长篇大论

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!