关于 precise-any-variants
precise-any-variants 是一项 TypeScript 指导技能,说明无法避免 `any` 时如何选择更具体的替代类型。它帮助开发者保留部分类型检查,并解释 `unknown` 更安全的场景。
主要功能
- 01建议使用 `any[]` 等具体类型
- 02用 `Record` 表示对象输入
- 03用函数类型表示可调用输入
- 04说明更安全地使用 `unknown`
- 05展示 TypeScript 示例和常见问题
适用场景
- 01为接收动态数据的函数添加类型
- 02将代码从 JavaScript 迁移
- 03检查过于宽泛的 `any` 注解
- 04使用更安全的类型解析 JSON
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill precise-any-variants安装后在 Claude Code 中输入/precise-any-variants确认已加载。
详细介绍
优先使用更精确的 any 变体,而不是普通 any
概述
如果必须使用 any,就尽可能具体。
普通 any 接受所有内容。但 any[]、Record<string, any> 或 () => any 的范围更窄,同时仍能提供一些类型检查。
适用场景
- 因某种原因不得不使用 any
- 编写接受“任何内容”的函数
- 处理真正动态的数据
- 从 JavaScript 迁移
黄金法则
any is a last resort.
Specific variants of any preserve partial type safety.
请记住:
any[]会检查它是否为数组Record<string, any>会检查它是否为对象() => any会检查它是否为函数unknown比任何 any 变体都更安全
检测:范围过宽的 any
function getLength(x: any) { // Too broad!
return x.length;
}
getLength(123); // No error, crashes at runtime
getLength(null); // No error, crashes at runtime
getLength([1,2,3]); // OK
更好的做法:使用具体的 any 变体
用 any[] 表示数组
function getLength(array: any[]) {
return array.length; // Type checked!
}
getLength([1, 2, 3]); // OK
getLength(/regex/);
// ~~~~~~~
// Argument of type 'RegExp' is not assignable to parameter of type 'any[]'
优点:
.length访问会进行类型检查- 返回类型是
number,而不是any - 非数组会被拒绝
用 Record<string, any> 表示对象
function hasKey(obj: Record<string, any>, key: string): boolean {
return key in obj;
}
hasKey({ a: 1 }, 'a'); // OK
hasKey(null, 'a');
// ~~~~
// Argument of type 'null' is not assignable to parameter of type 'Record<string, any>'
用 () => any 表示函数
type Fn0 = () => any; // No params
type Fn1 = (arg: any) => any; // One param
type FnN = (...args: any[]) => any; // Any params
function callTwice(fn: FnN) {
fn();
fn();
}
callTwice(() => console.log('hi')); // OK
callTwice(123);
// ~~~
// Argument of type 'number' is not assignable to parameter of type '(...args: any[]) => any'
用 any[] 表示剩余参数
// any rest parameter: return type is any
const numArgsBad = (...args: any) => args.length;
// ^? (...args: any) => any
// any[] rest parameter: return type is number
const numArgsBetter = (...args: any[]) => args.length;
// ^? (...args: any[]) => number
返回类型会影响下游代码!
any 变体对比
| 类型 | 接受 | 拒绝 |
|---|---|---|
any | 所有内容 | 无 |
any[] | 数组 | 非数组 |
Record<string, any> | 对象 | 原始类型、null |
() => any | 函数 | 非函数 |
object | 对象、数组 | 原始类型、null |
unknown | 所有内容 | 所有内容(未经检查时) |
考虑改用 unknown
unknown 更安全:
function process(data: unknown) {
// Must narrow before using
if (Array.isArray(data)) {
data.length; // OK, data is any[]
}
if (typeof data === 'object' && data !== null) {
// data is object
}
}
使用 unknown 时,必须先检查类型,之后才能进行任何操作。
实际示例:解析 JSON
// Don't return any
function parseBad(json: string): any {
return JSON.parse(json);
}
// Better: return unknown
function parseGood(json: string): unknown {
return JSON.parse(json);
}
// Caller must narrow:
const data = parseGood('{"x": 1}');
if (typeof data === 'object' && data !== null && 'x' in data) {
console.log(data.x);
}
无法避免使用 any 的情况
有时你确实需要 any:
// Wrapping a library that uses any internally
function wrapLibrary<T>(input: T): T {
return (library as any).process(input);
}
// Type assertion in implementation
function merge<T>(a: Partial<T>, b: Partial<T>): T {
return { ...a, ...b } as any as T;
}
即便如此,也应将它隐藏在类型签名良好的函数中(条目 45)。
抵御压力的准则
1. “any 能用就行”
压力:“直接用 any,继续往下做”
**回应:**any 会禁用所有类型检查。具体的变体可以保留一定的安全性。
**行动:**使用可行的最具体变体。
2. “我不知道类型”
压力:“这个类型确实是动态的”
**回应:**对于真正未知的类型,unknown 更安全。
**行动:**使用 unknown,然后在使用前缩小类型范围。
危险信号——停下来重新考虑
- 将
any用作函数参数(改用具体的变体) - 将
any用作返回类型(优先使用 unknown) - 用
any表示对象(改用 Record<string, any> 或 object) - 用
any表示数组(改用 any[])
常见借口(都不成立)
| 借口 | 实际情况 |
|---|---|
| “它太动态了,无法定义类型” | unknown 可以处理真正动态的数据 |
| “具体的变体太冗长” | 多写几个字符,可以避免运行时错误 |
| “内部代码用 any 没问题” | 内部代码同样会有 bug |
快速参考
// DON'T: Plain any
function f(x: any): any { ... }
// DO: Specific variants
function getLength(arr: any[]): number { ... }
function getKeys(obj: Record<string, any>): string[] { ... }
function call(fn: (...args: any[]) => any): void { ... }
// BEST: unknown when possible
function process(data: unknown): void {
if (Array.isArray(data)) { ... }
}
总而言之
如果必须使用 any,就让它具体一些。
any[]、Record<string, any> 和 () => any 在保持灵活性的同时,仍能保留一些类型检查。但请考虑 unknown 是否是更安全的选择。
参考资料
基于 Dan Vanderkam 所著《Effective TypeScript》第 44 条:优先使用更精确的 any 变体,而不是普通 any。
评论
支持 Markdown