mcpField
P
prefer-unknown-over-any

prefer-unknown-over-any

github.com/marius-townhouse开发658 次安装39 周前更新
代码仓库

关于 prefer-unknown-over-any

面向 TypeScript 开发者的编码技能,建议在值的类型未知时使用 `unknown` 而非 `any`。它通过外部数据、解析内容和灵活函数参数的示例,说明如何收窄类型并进行验证。

主要功能

  1. 01未知类型优先使用 `unknown`
  2. 02使用前收窄类型
  3. 03验证 JSON、YAML 和 API 数据
  4. 04使用类型守卫与断言

适用场景

  1. 01安全解析 JSON 或 YAML
  2. 02处理外部或用户输入的数据
  3. 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:关键区别

属性anyunknown
任何值都可以赋给它是是
可以赋给任何类型是否
可以调用方法是(危险!)否(安全!)
使用前必须缩小类型范围否是

安全地使用 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。

评论

支持 Markdown