关于 typescript-strict-mode
面向开发者的 TypeScript 严格类型实践指南,介绍 `any` 的替代方案、类型收窄、可复用类型和 ESLint 规则,以减少不安全的类型使用。
主要功能
- 01用更安全的类型替代 `any`
- 02通过类型守卫收窄 `unknown`
- 03使用泛型和联合类型
- 04为回调与异步函数标注类型
- 05配置严格的 ESLint 规则
适用场景
- 01检查 TypeScript 代码中的不安全类型
- 02为 JSON 数据和动态对象添加类型
- 03在代码库中设置更严格的 lint 规则
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ op0ai/ claude-skills --skill typescript-strict-mode安装后在 Claude Code 中输入/typescript-strict-mode确认已加载。
详细介绍
TypeScript 严格模式最佳实践
概述
本指南介绍适用于所有框架的 TypeScript 严格实践,重点讲解如何避免使用 any、正确添加类型注解,以及如何利用 TypeScript 的类型系统编写更安全、更易维护的代码。
黄金法则:绝不使用 any
重要规则: 许多代码库都启用了 @typescript-eslint/no-explicit-any。使用 any 会导致构建失败。
any 的危险之处:
- 破坏 TypeScript 类型系统的作用
- 隐藏本可在编译时发现的错误
- 导致整个代码库的类型安全性降低
- 使重构变得困难且容易出错
any 的替代方案
1. 使用具体类型
❌ 错误:
function processData(data: any) { ... }
const items: any[] = [];
✅ 正确:
function processData(data: { id: string; name: string }) { ... }
const items: string[] = [];
2. 类型确实未知时使用 unknown
unknown 是 any 的类型安全版本。它会强制你在使用前缩小类型范围。
❌ 错误:
function handleResponse(response: any) {
return response.data.name; // No type checking!
}
✅ 正确:
function handleResponse(response: unknown) {
if (
typeof response === "object" &&
response !== null &&
"data" in response &&
typeof (response as { data: unknown }).data === "object"
) {
const data = (response as { data: { name: string } }).data;
return data.name;
}
throw new Error("Invalid response format");
}
3. 对可复用组件使用泛型
❌ 错误:
function wrapValue(value: any): { wrapped: any } {
return { wrapped: value };
}
✅ 正确:
function wrapValue<T>(value: T): { wrapped: T } {
return { wrapped: value };
}
// Usage
const wrappedString = wrapValue("hello"); // { wrapped: string }
const wrappedNumber = wrapValue(42); // { wrapped: number }
4. 对多种可能性使用联合类型
❌ 错误:
function handleInput(input: any) {
if (typeof input === 'string') { ... }
if (typeof input === 'number') { ... }
}
✅ 正确:
function handleInput(input: string | number) {
if (typeof input === 'string') { ... }
if (typeof input === 'number') { ... }
}
5. 使用类型守卫进行运行时检查
interface User {
id: string;
name: string;
email: string;
}
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
"name" in value &&
"email" in value &&
typeof (value as User).id === "string" &&
typeof (value as User).name === "string" &&
typeof (value as User).email === "string"
);
}
function processUser(data: unknown) {
if (isUser(data)) {
// data is now typed as User
console.log(data.name);
}
}
6. 对动态对象使用 Record<K, V>
❌ 错误:
const cache: any = {};
cache["key"] = "value";
✅ 正确:
const cache: Record<string, string> = {};
cache["key"] = "value";
// Or with specific keys
const userSettings: Record<"theme" | "language", string> = {
theme: "dark",
language: "en",
};
7. 对灵活对象使用索引签名
interface Config {
name: string;
version: string;
[key: string]: string | number | boolean; // Additional properties
}
const config: Config = {
name: "my-app",
version: "1.0.0",
debug: true,
port: 3000,
};
常见事件处理程序类型
React 事件类型
// Form events
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// ...
};
// Input events
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
// ...
};
// Click events
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
// ...
};
// Keyboard events
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') { ... }
};
// Focus events
const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {
// ...
};
DOM 事件类型(非 React)
// Generic DOM events
document.addEventListener('click', (e: MouseEvent) => { ... });
document.addEventListener('keydown', (e: KeyboardEvent) => { ... });
document.addEventListener('submit', (e: SubmitEvent) => { ... });
Promise 和异步类型
为异步函数添加类型
// Function returning a promise
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// Arrow function variant
const fetchUser = async (id: string): Promise<User> => {
const response = await fetch(`/api/users/${id}`);
return response.json();
};
Promise 类型模式
// Promise with explicit type
const userPromise: Promise<User> = fetchUser("123");
// Awaiting with type inference
const user = await fetchUser("123"); // User
// Promise.all with multiple types
const [user, posts] = await Promise.all([fetchUser("123"), fetchPosts("123")]); // [User, Post[]]
函数类型
回调类型
// Typed callback parameter
function processItems(
items: string[],
callback: (item: string, index: number) => void
) {
items.forEach(callback);
}
// Alternative: Extract the type
type ItemCallback = (item: string, index: number) => void;
function processItems(items: string[], callback: ItemCallback) {
items.forEach(callback);
}
重载函数
// Function overloads for different input/output types
function parse(input: string): object;
function parse(input: Buffer): object;
function parse(input: string | Buffer): object {
if (typeof input === "string") {
return JSON.parse(input);
}
return JSON.parse(input.toString());
}
类型断言(谨慎使用)
仅在你比 TypeScript 更了解类型时使用类型断言:
// DOM element assertion (when you know the element type)
const input = document.getElementById("email") as HTMLInputElement;
// Response data assertion (when you trust the API)
const data = (await response.json()) as ApiResponse;
// Non-null assertion (when you know it's not null)
const element = document.querySelector(".button")!;
警告: 类型断言会绕过 TypeScript 的检查。条件允许时,优先使用类型守卫。
工具类型
内置工具类型
// Partial - all properties optional
type PartialUser = Partial<User>;
// Required - all properties required
type RequiredUser = Required<User>;
// Pick - select specific properties
type UserName = Pick<User, "name" | "email">;
// Omit - exclude specific properties
type UserWithoutId = Omit<User, "id">;
// Readonly - immutable properties
type ReadonlyUser = Readonly<User>;
// Record - create object type
type UserMap = Record<string, User>;
// ReturnType - extract function return type
type FetchUserReturn = ReturnType<typeof fetchUser>;
// Parameters - extract function parameters
type FetchUserParams = Parameters<typeof fetchUser>;
可辨识联合
处理多种相关类型的模式:
type Result<T> = { success: true; data: T } | { success: false; error: string };
function handleResult<T>(result: Result<T>) {
if (result.success) {
// TypeScript knows result.data exists here
console.log(result.data);
} else {
// TypeScript knows result.error exists here
console.error(result.error);
}
}
模块扩充
无需修改原始类型即可扩展现有类型:
// Extend Express Request
declare module "express" {
interface Request {
user?: User;
}
}
// Extend environment variables
declare global {
namespace NodeJS {
interface ProcessEnv {
DATABASE_URL: string;
API_KEY: string;
}
}
}
常见陷阱
陷阱 1:对 JSON 数据使用 any
❌ 错误:
const data: any = JSON.parse(jsonString);
✅ 正确:
interface ExpectedData {
id: string;
name: string;
}
const data: unknown = JSON.parse(jsonString);
// Then validate with type guard or schema validation (zod, etc.)
陷阱 2:回调中的隐式 any
❌ 错误:
// 'item' has implicit 'any' type
items.map((item) => item.name);
✅ 正确:
items.map((item: Item) => item.name);
// Or ensure 'items' has proper type: Item[]
陷阱 3:对象属性访问
❌ 错误:
function getValue(obj: any, key: string) {
return obj[key];
}
✅ 正确:
function getValue<T extends Record<string, unknown>, K extends keyof T>(
obj: T,
key: K
): T[K] {
return obj[key];
}
陷阱 4:空数组类型
❌ 错误:
const items = []; // any[]
✅ 正确:
const items: string[] = [];
// or
const items: Array<string> = [];
应启用的 ESLint 规则
要启用严格的 TypeScript 检查,请开启以下规则:
{
"rules": {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/strict-boolean-expressions": "warn",
"@typescript-eslint/no-unsafe-assignment": "error",
"@typescript-eslint/no-unsafe-member-access": "error",
"@typescript-eslint/no-unsafe-call": "error",
"@typescript-eslint/no-unsafe-return": "error"
}
}
注意:不要使用已弃用的
@typescript-eslint/no-implicit-any-catch规则,而应在tsconfig.json中设置useUnknownInCatchVariables: true(TypeScript 4.4+)。这样可确保 catch 子句中的变量类型为unknown,而不是any。
快速参考
| 替代 any 的场景 | 使用 |
| ---------------- | ------------------------ | --- |
| 未知数据 | unknown |
| 灵活类型 | 泛型 <T> |
| 多种类型 | 联合类型 A | B |
| 动态键 | Record<K, V> |
| 可为 null | T \| null |
| 可选 | T \| undefined 或 T? |
| 回调 | (args) => ReturnType |
| 空数组 | Type[] |
| JSON 数据 | unknown + 类型守卫 |
总结
- 绝不使用
any——它会破坏 TypeScript 的设计初衷 - 对确实未知的类型使用
unknown,然后通过类型守卫缩小类型范围 - 使用泛型编写可复用且类型安全的组件
- 对有限的可能性集合使用联合类型
- 对复杂状态机使用可辨识联合
- 启用严格的 ESLint 规则,自动发现违规情况