precise-any-variants

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Prefer More Precise Variants of any to Plain any

优先使用更精确的any变体而非普通any

Overview

概述

If you must use any, make it as specific as possible.
Plain
any
accepts everything. But
any[]
,
Record<string, any>
, or
() => any
are narrower and still provide some type checking.
如果你必须使用any,请尽可能让它更具体。
普通的
any
接受所有类型,但
any[]
Record<string, any>
() => any
的范围更窄,同时仍能提供一定的类型检查。

When to Use This Skill

何时使用此技巧

  • Forced to use any for some reason
  • Writing functions that accept "anything"
  • Dealing with truly dynamic data
  • Migrating from JavaScript
  • 因某些原因不得不使用any
  • 编写接受“任意内容”的函数
  • 处理真正动态的数据
  • 从JavaScript迁移到TypeScript

The Iron Rule

铁则

any is a last resort.
Specific variants of any preserve partial type safety.
Remember:
  • any[]
    checks that it's an array
  • Record<string, any>
    checks that it's an object
  • () => any
    checks that it's a function
  • unknown
    is even safer than any variant
any是最后的选择。
更具体的any变体可保留部分类型安全性。
请记住:
  • any[]
    会检查是否为数组
  • Record<string, any>
    会检查是否为对象
  • () => any
    会检查是否为函数
  • unknown
    比任何any变体都更安全

Detection: Over-broad any

检测:过于宽泛的any

typescript
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
typescript
function getLength(x: any) {  // 过于宽泛!
  return x.length;
}

getLength(123);     // 无报错,但运行时会崩溃
getLength(null);    // 无报错,但运行时会崩溃
getLength([1,2,3]); // 正常

Better: Specific any Variants

更好的选择:具体的any变体

any[] for Arrays

用any[]表示数组

typescript
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[]'
Benefits:
  • .length
    access is type-checked
  • Return type is
    number
    , not
    any
  • Non-arrays are rejected
typescript
function getLength(array: any[]) {
  return array.length;  // 已做类型检查!
}

getLength([1, 2, 3]);  // 正常
getLength(/regex/);
//        ~~~~~~~
// 类型“RegExp”的参数不能赋给类型“any[]”的参数
优势:
  • .length
    的访问会被类型检查
  • 返回类型为
    number
    ,而非
    any
  • 非数组会被拒绝

Record<string, any> for Objects

用Record<string, any>表示对象

typescript
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>'
typescript
function hasKey(obj: Record<string, any>, key: string): boolean {
  return key in obj;
}

hasKey({ a: 1 }, 'a');  // 正常
hasKey(null, 'a');
//     ~~~~
// 类型“null”的参数不能赋给类型“Record<string, any>”的参数

() => any for Functions

用() => any表示函数

typescript
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'
typescript
type Fn0 = () => any;           // 无参数
type Fn1 = (arg: any) => any;   // 一个参数
type FnN = (...args: any[]) => any;  // 任意数量参数

function callTwice(fn: FnN) {
  fn();
  fn();
}

callTwice(() => console.log('hi'));  // 正常
callTwice(123);
//        ~~~
// 类型“number”的参数不能赋给类型“(...args: any[]) => any”的参数

Use any[] for Rest Parameters

用any[]表示剩余参数

typescript
// 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
The return type matters for downstream code!
typescript
// any剩余参数:返回类型为any
const numArgsBad = (...args: any) => args.length;
//    ^? (...args: any) => any

// any[]剩余参数:返回类型为number
const numArgsBetter = (...args: any[]) => args.length;
//    ^? (...args: any[]) => number
返回类型对下游代码至关重要!

Comparison of any Variants

any变体对比

TypeAcceptsRejects
any
EverythingNothing
any[]
ArraysNon-arrays
Record<string, any>
ObjectsPrimitives, null
() => any
FunctionsNon-functions
object
Objects, arraysPrimitives, null
unknown
EverythingEverything (without check)
类型接受拒绝
any
所有内容
any[]
数组非数组
Record<string, any>
对象原始类型、null
() => any
函数非函数
object
对象、数组原始类型、null
unknown
所有内容所有内容(未检查时)

Consider unknown Instead

考虑使用unknown替代

unknown
is even safer:
typescript
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
  }
}
With
unknown
, you can't do anything without first checking the type.
unknown
更加安全:
typescript
function process(data: unknown) {
  // 使用前必须先收窄类型
  if (Array.isArray(data)) {
    data.length;  // 正常,data类型为any[]
  }
  if (typeof data === 'object' && data !== null) {
    // data类型为object
  }
}
使用
unknown
时,你必须先检查类型才能进行操作。

Real-World Example: JSON Parsing

实际案例:JSON解析

typescript
// 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);
}
typescript
// 不要返回any
function parseBad(json: string): any {
  return JSON.parse(json);
}

// 更好的选择:返回unknown
function parseGood(json: string): unknown {
  return JSON.parse(json);
}

// 调用者必须收窄类型:
const data = parseGood('{"x": 1}');
if (typeof data === 'object' && data !== null && 'x' in data) {
  console.log(data.x);
}

When any is Unavoidable

当any不可避免时

Sometimes you genuinely need any:
typescript
// 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;
}
Even then, hide it inside functions with good type signatures (Item 45).
有时你确实需要使用any:
typescript
// 封装一个内部使用any的库
function wrapLibrary<T>(input: T): T {
  return (library as any).process(input);
}

// 实现中的类型断言
function merge<T>(a: Partial<T>, b: Partial<T>): T {
  return { ...a, ...b } as any as T;
}
即便如此,也要把它隐藏在具有良好类型签名的函数内部(参考第45条)。

Pressure Resistance Protocol

压力应对方案

1. "any Works"

1. “any能用就行”

Pressure: "Just use any and move on"
Response: any disables ALL type checking. Specific variants preserve some safety.
Action: Use the most specific variant that works.
压力:“直接用any就行,继续往下做”
**回应:**any会禁用所有类型检查。具体的变体可保留部分安全性。
**行动:**使用能满足需求的最具体变体。

2. "I Don't Know the Type"

2. “我不知道类型”

Pressure: "The type is truly dynamic"
Response: unknown is safer for truly unknown types.
Action: Use unknown, then narrow before using.
压力:“类型确实是动态的”
**回应:**对于真正未知的类型,unknown更安全。
**行动:**使用unknown,然后在使用前收窄类型。

Red Flags - STOP and Reconsider

危险信号 - 停止并重新考虑

  • any
    as function parameter (use specific variant)
  • any
    as return type (prefer unknown)
  • any
    for objects (use Record<string, any> or object)
  • any
    for arrays (use any[])
  • any
    作为函数参数(使用具体变体)
  • any
    作为返回类型(优先使用unknown)
  • any
    表示对象(使用Record<string, any>或object)
  • any
    表示数组(使用any[])

Common Rationalizations (All Invalid)

常见的合理化借口(均无效)

ExcuseReality
"It's too dynamic to type"unknown handles truly dynamic data
"Specific variants are verbose"A few characters save runtime errors
"any is fine for internal code"Internal code still has bugs
借口事实
“它太动态了,无法定义类型”unknown可以处理真正动态的数据
“具体变体太啰嗦”多写几个字符能避免运行时错误
“内部代码用any没问题”内部代码也会有bug

Quick Reference

快速参考

typescript
// 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)) { ... }
}
typescript
// 不要:普通any
function f(x: any): any { ... }

// 要:具体变体
function getLength(arr: any[]): number { ... }
function getKeys(obj: Record<string, any>): string[] { ... }
function call(fn: (...args: any[]) => any): void { ... }

// 最佳选择:尽可能使用unknown
function process(data: unknown): void {
  if (Array.isArray(data)) { ... }
}

The Bottom Line

总结

If you must use any, make it specific.
any[]
,
Record<string, any>
, and
() => any
preserve some type checking while still allowing flexibility. But consider whether
unknown
is a safer choice.
如果你必须使用any,请让它更具体。
any[]
Record<string, any>
() => any
在保留灵活性的同时,还能保留一定的类型检查。但请考虑
unknown
是否是更安全的选择。

Reference

参考资料

Based on "Effective TypeScript" by Dan Vanderkam, Item 44: Prefer More Precise Variants of any to Plain any.
基于Dan Vanderkam所著《Effective TypeScript》中的第44条:优先使用更精确的any变体而非普通any。