关于 prefer-unknown-over-any
面向 TypeScript 开发者的编码技能,建议在值的类型未知时使用 `unknown` 而非 `any`。它通过外部数据、解析内容和灵活函数参数的示例,说明如何收窄类型并进行验证。
主要功能
- 01未知类型优先使用 `unknown`
- 02使用前收窄类型
- 03验证 JSON、YAML 和 API 数据
- 04使用类型守卫与断言
适用场景
- 01安全解析 JSON 或 YAML
- 02处理外部或用户输入的数据
- 03替换不安全的 `any` 类型
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill prefer-unknown-over-any安装后在 Claude Code 中输入/prefer-unknown-over-any确认已加载。
详细介绍
对类型未知的值使用 unknown,而不是 any
概述
unknown 是 any 的类型安全近亲。
当你不知道某个值的类型时,请使用 unknown。它会要求你在使用该值之前先缩小类型范围,从而避免 any 会放过的错误。
何时使用此技巧
- 解析 JSON 或 YAML
- 接收来自外部来源的数据
- 处理用户输入
- 编写接受任意值的函数
- 用更安全的类型替换 any
铁律
NEVER use any when unknown would work.
没有例外:
- 不能因为“unknown 更啰嗦”
- 不能因为“我知道这样是安全的”
- 不能因为“反正我会在运行时检查”
检测:“未知数据使用 any”的异味
如果你因为不知道类型而使用 any,请改用 unknown。
// ❌ VIOLATION: any allows dangerous operations
function parseYAML(yaml: string): any {
// ...
}
const data = parseYAML('...');
data.foo.bar.baz(); // No error! But will crash
data('call me maybe'); // No error! But will crash
// ✅ CORRECT: unknown forces you to check first
function safeParseYAML(yaml: string): unknown {
// ...
}
const data = safeParseYAML('...');
data.foo.bar.baz(); // Error: 'data' is of type 'unknown'
data('call me maybe'); // Error: 'data' is of type 'unknown'
any 与 unknown:关键区别
| 属性 | any | unknown |
|---|---|---|
| 任何值都可以赋给它 | 是 | 是 |
| 可以赋给任何类型 | 是 | 否 |
| 可以调用方法 | 是(危险!) | 否(安全!) |
| 使用前必须缩小类型范围 | 否 | 是 |
安全地使用 unknown
使用类型断言(当你有把握时)
interface Book {
name: string;
author: string;
}
const data = safeParseYAML(`
name: Wuthering Heights
author: Emily Bronte
`) as Book; // You assert it's a Book
console.log(data.name); // OK
使用 instanceof(针对类)
function processValue(value: unknown) {
if (value instanceof Date) {
value.toISOString(); // OK - value is Date
}
}
使用 typeof(针对基本类型)
function processValue(value: unknown) {
if (typeof value === 'string') {
value.toUpperCase(); // OK - value is string
}
if (typeof value === 'number') {
value.toFixed(2); // OK - value is number
}
}
使用类型守卫(进行自定义检查)
interface Book {
name: string;
author: string;
}
function isBook(value: unknown): value is Book {
return (
typeof value === 'object' &&
value !== null &&
'name' in value &&
'author' in value
);
}
function processValue(value: unknown) {
if (isBook(value)) {
console.log(value.name, value.author); // OK - value is Book
}
}
常见用例
JSON 解析
// ❌ BAD: JSON.parse returns any by default
const data = JSON.parse(response);
data.anything.goes(); // No error, but might crash
// ✅ GOOD: Parse as unknown, then validate
function parseJSON(text: string): unknown {
return JSON.parse(text);
}
const data = parseJSON(response);
if (isValidResponse(data)) {
// Now data is the validated type
}
API 响应
// ❌ BAD: Trusting the response
async function fetchUser(id: string): Promise<any> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// ✅ GOOD: Type unknown, validate before use
async function fetchUser(id: string): Promise<unknown> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
const user = await fetchUser('123');
if (isUser(user)) {
console.log(user.name); // Safe!
}
通用的“接受任意值”函数
// ❌ BAD: any loses all type info
function isSmallArray(arr: any[]): boolean {
return arr.length < 10;
}
// ✅ GOOD: unknown[] is safer
function isSmallArray(arr: readonly unknown[]): boolean {
return arr.length < 10;
}
缩小 unknown 的类型范围
TypeScript 要求先提供依据,才允许你使用 unknown:
function process(value: unknown) {
// Direct use: Error
value.toString(); // Error: 'value' is of type 'unknown'
// After null check
if (value !== null && value !== undefined) {
// Still unknown, but non-nullish
}
// After typeof check
if (typeof value === 'string') {
value.toUpperCase(); // OK: value is string
}
// After instanceof check
if (value instanceof Date) {
value.toISOString(); // OK: value is Date
}
// After type guard
if (isBook(value)) {
value.author; // OK: value is Book
}
}
object、{} 和 unknown
| 类型 | 包含 | 不包含 |
|---|---|---|
unknown | 所有值 | 不排除任何值 |
{} | 除 null/undefined 外的所有值 | null、undefined |
object | 对象、数组、函数 | 基本类型、null、undefined |
// unknown is most broad - prefer it for "I don't know"
function acceptAnything(value: unknown) {}
// {} excludes null/undefined
function acceptNonNullish(value: {}) {}
// object excludes primitives
function acceptObject(value: object) {}
抵御压力的准则
1. “unknown 太啰嗦了”
压力:“我得在每个地方都添加检查”
**回应:**这些检查能捕获错误。any 则会放过错误。
**行动:**编写这些检查。它们也是文档。
2. “我在运行时知道类型”
压力:“我会先检查类型再使用”
**回应:**使用 unknown 时,TypeScript 会验证你的检查是否正确。
**行动:**使用 unknown,让 TypeScript 跟踪类型范围的缩小过程。
3. “JSON.parse 返回 any”
压力:“内置函数返回 any”
**回应:**将它封装在一个返回 unknown 的函数中。
**行动:**创建一个返回 unknown 的 safeParseJSON 辅助函数。
危险信号——停下来重新考虑
- 对来自外部来源的值使用
any - 对解析后的 JSON/YAML 使用
any - 对 API 响应体使用
any - 仅仅因为“我不知道类型”就使用
any - 在可以接受
unknown时仍接受any的函数
常见的自我辩解(全部无效)
| 借口 | 事实 |
|---|---|
| “我会在运行时检查” | unknown 会让 TypeScript 验证你的检查。 |
| “太啰嗦了” | 冗长的代码能捕获错误。 |
| “any 更简单” | 编写起来更简单,调试起来更困难。 |
| “我信任数据来源” | 外部来源永远不可信。 |
快速参考
| 情况 | 使用 |
|---|---|
| 不知道类型 | unknown |
| 解析 JSON/YAML | 返回 unknown,然后进行验证 |
| 外部 API 数据 | 验证前使用 unknown |
| 接受任意值 | unknown(而非 any) |
| 任意值组成的数组 | unknown[](而非 any[]) |
总结
unknown 表示“我不知道这个类型”。any 表示“我不在乎类型”。
当你确实不知道某个值的类型时,请使用 unknown。它会要求你在使用前缩小类型范围,从而捕获 any 会漏掉的错误。这样你的代码会更安全,也能更明确地说明类型信息的来源。
参考资料
基于 Dan Vanderkam 的《Effective TypeScript》第 46 条:对类型未知的值使用 unknown,而不是 any。