mcpField
P
precise-any-variants

precise-any-variants

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

关于 precise-any-variants

precise-any-variants 是一项 TypeScript 指导技能,说明无法避免 `any` 时如何选择更具体的替代类型。它帮助开发者保留部分类型检查,并解释 `unknown` 更安全的场景。

主要功能

  1. 01建议使用 `any[]` 等具体类型
  2. 02用 `Record` 表示对象输入
  3. 03用函数类型表示可调用输入
  4. 04说明更安全地使用 `unknown`
  5. 05展示 TypeScript 示例和常见问题

适用场景

  1. 01为接收动态数据的函数添加类型
  2. 02将代码从 JavaScript 迁移
  3. 03检查过于宽泛的 `any` 注解
  4. 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