关于 avoid-wrapper-types
这项 TypeScript 技能说明应使用 `string`、`number` 等原始类型,而非对象包装类型,帮助开发者避免类型不匹配及运行时问题。
主要功能
- 01使用小写原始类型
- 02识别包装类型不匹配
- 03了解对象身份与真值陷阱
- 04通过自动装箱使用原始值方法
适用场景
- 01为 TypeScript 原始值标注类型
- 02排查包装类型赋值错误
- 03检查代码中的意外包装对象
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill avoid-wrapper-types安装后在 Claude Code 中输入/avoid-wrapper-types确认已加载。
详细介绍
避免使用对象包装类型(String、Number、Boolean、Symbol、BigInt)
概述
使用小写原始类型,绝不使用大写包装类型。
JavaScript 有原始类型(string、number、boolean)和对象包装类型(String、Number、Boolean)。TypeScript 同时支持这两类类型,但你几乎不应该使用包装类型。
何时使用本指南
- 为任何原始值指定类型时
- 遇到有关包装类型不匹配的错误时
- 想了解为什么
String !== string时 - 使用原始类型的方法时
铁律
ALWAYS use lowercase: string, number, boolean, symbol, bigint
NEVER use uppercase: String, Number, Boolean, Symbol, BigInt
请记住:
- 包装类型是对象,原始类型不是
- 需要包装类型时,TypeScript 会直接接受原始类型
- 需要原始类型时,TypeScript 不会接受包装类型
- 原始类型通过自动装箱来调用方法
检测:包装类型陷阱
// These look similar but behave differently
const primitive: string = "hello";
const wrapper: String = new String("hello");
// Equality fails
primitive === wrapper; // false (different types)
wrapper === wrapper; // Wait, this is also comparing to itself...
const s1 = new String("hello");
const s2 = new String("hello");
s1 === s2; // false! Each wrapper is a unique object
原始类型的方法如何工作
原始类型没有方法,但下面的代码可以正常运行:
'primitive'.charAt(3); // 'm' - how?
JavaScript 会自动装箱:
- 创建临时的
String包装对象 - 在包装对象上调用方法
- 丢弃包装对象
- 返回结果
这样既能方便地调用方法,又能避免包装类型的缺点。
类型赋值问题
// Primitive assignable to wrapper (OK but don't do this)
const s: String = "primitive"; // Works
// Wrapper NOT assignable to primitive (Error)
function takesString(s: string) { }
takesString(new String("hello"));
// ~~~~~~~~~~~~~~~~~~~
// Argument of type 'String' is not assignable to parameter of type 'string'.
// 'string' is a primitive, but 'String' is a wrapper object.
所有原始类型
| 原始类型(使用) | 包装类型(避免) |
|---|---|
string | String |
number | Number |
boolean | Boolean |
symbol | Symbol |
bigint | BigInt |
注意:null 和 undefined 没有包装类型。
包装类型的问题
标识问题
const s1 = new String("hello");
const s2 = new String("hello");
s1 == s2; // false
s1 === s2; // false
// Each is a distinct object
typeof 带来的困惑
typeof "hello"; // "string"
typeof new String("hello"); // "object"
真值判断陷阱
const falsy = new Boolean(false);
if (falsy) {
console.log("This runs!"); // Wrapper objects are always truthy
}
例外:运行时方法
你可以使用包装类型的静态方法:
// These are fine - not creating wrapper objects
String.fromCharCode(65); // "A"
Number.isNaN(x);
BigInt(123); // Creates primitive, not wrapper
但绝不要将它们用作类型,也不要创建实例。
抵制压力指南
1. “我需要 String 方法”
压力:“我想使用 String 方法,所以我要把类型写成 String”
**回应:**原始类型 string 通过自动装箱也拥有所有相同的方法。
**行动:**使用 string 类型。方法可以直接调用。
2. “TypeScript 推断出了 String(大写)”
压力:“TypeScript 给出了 String,所以这肯定没错”
**回应:**检查你的代码——你可能不小心创建了包装对象。
**行动:**找到并修复源头。使用小写类型。
危险信号——停下来重新考虑
- 类型注解中出现大写的
String、Number、Boolean - 任何位置出现
new String()、new Number()、new Boolean() - 有关包装类型与原始类型赋值的错误
- 预期值为原始类型时,却检查
typeof x === "object"
常见借口(都不成立)
| 借口 | 事实 |
|---|---|
| “String 有更多方法” | 不,两者的方法完全相同 |
| “我需要一个对象” | 你很少需要;原始类型更简单 |
| “这是一回事” | 不,包装类型会带来标识和真值判断问题 |
快速参考
// ALWAYS use these
const s: string = "hello";
const n: number = 42;
const b: boolean = true;
const sym: symbol = Symbol();
const big: bigint = 9007199254740991n;
// NEVER use these
const s: String = "hello"; // Wrong
const n: Number = 42; // Wrong
const b: Boolean = true; // Wrong
总结
始终使用小写原始类型。
绝没有理由将 String、Number 或 Boolean 用作类型。大写形式会导致赋值错误和标识问题,而且相比小写原始类型毫无优势。
参考
基于 Dan Vanderkam 的《Effective TypeScript》第 10 条:避免使用对象包装类型(String、Number、Boolean、Symbol、BigInt)。
评论
支持 Markdown