关于 precise-string-types
面向 TypeScript 开发者的编码技能,指导在字符串有效值有限时使用更精确的类型。内容涵盖字面量联合、keyof 和模板字面量类型,以改善安全性与表达清晰度。
主要功能
- 01字符串字面量联合指导
- 02用 keyof 表示对象键
- 03用模板字面量类型描述模式
- 04类型安全的 API 参数建议
- 05字符串类型迁移步骤
适用场景
- 01将参数限制为有效值
- 02将函数参数限制为对象键
- 03为具有固定模式的字符串定义类型
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill precise-string-types安装后在 Claude Code 中输入/precise-string-types确认已加载。
详细介绍
优先选择比 String 类型更精确的替代方案
概述
String 类型范围极广。尽可能使用范围更窄的类型。
字符串字面量联合类型、模板字面量类型和 keyof 比普通的 string 更安全,能提供更好的自动补全和文档说明。
何时使用此技能
- 定义有效值有限的属性
- 创建只接受特定字符串的函数参数
- 处理对象键或属性名称
- 发现“字符串类型化”的代码
- 字符串参数没有自动补全
铁律
NEVER use plain string when the valid values are limited.
没有例外:
- 不能因为“以后可能会有更多值”
- 不能因为“这样更简单”
- 不能因为“枚举太大材小用”
检测:“字符串类型化”的异味
如果字符串只能是某些特定值,就应明确表达这一点。
// ❌ VIOLATION: Too broad - accepts any string
interface Album {
artist: string;
title: string;
releaseDate: string; // What format? Any string works!
recordingType: string; // "live" or "studio"... but also "banana"
}
// These are all valid but wrong:
const album: Album = {
artist: 'Miles Davis',
title: 'Kind of Blue',
releaseDate: 'August 17th, 1959', // Wrong format
recordingType: 'Studio', // Wrong case
};
解决方案:字符串字面量联合类型
// ✅ CORRECT: Precise types
type RecordingType = 'studio' | 'live';
interface Album {
artist: string;
title: string;
releaseDate: Date; // Use Date, not string
recordingType: RecordingType;
}
const album: Album = {
artist: 'Miles Davis',
title: 'Kind of Blue',
releaseDate: new Date('1959-08-17'),
recordingType: 'Studio',
// ~~~~~~~~ Type '"Studio"' is not assignable to type 'RecordingType'
};
精确类型的优势
1. 捕获拼写错误
type Direction = 'north' | 'south' | 'east' | 'west';
function move(direction: Direction) { /* ... */ }
move('nroth'); // Error: Did you mean 'north'?
2. 提供自动补全
// With string: no suggestions
function move(direction: string) { }
move('|') // Nothing
// With union: suggestions appear
function move(direction: Direction) { }
move('|') // Suggests: north, south, east, west
3. 说明有效值
/** What environment was this recording made in? */
type RecordingType = 'live' | 'studio';
// The type IS the documentation
function getAlbums(type: RecordingType) { }
// IDE shows: (parameter) type: "live" | "studio"
使用 keyof 获取对象键
// ❌ BAD: Any string is accepted
function pluck(records: any[], key: string): any[] {
return records.map(r => r[key]);
}
// ✅ GOOD: Only valid keys accepted
function pluck<T>(records: T[], key: keyof T): T[keyof T][] {
return records.map(r => r[key]);
}
const albums: Album[] = [/* ... */];
pluck(albums, 'artist'); // OK, returns string[]
pluck(albums, 'artits'); // Error: Did you mean 'artist'?
更好的做法:泛型键参数
function pluck<T, K extends keyof T>(records: T[], key: K): T[K][] {
return records.map(r => r[key]);
}
const dates = pluck(albums, 'releaseDate');
// ^? const dates: Date[] // Precise return type!
const artists = pluck(albums, 'artist');
// ^? const artists: string[]
用于表达模式的模板字面量类型
// Match strings like "GET /users" or "POST /items"
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiRoute = `${HttpMethod} /${string}`;
function handleRoute(route: ApiRoute) { }
handleRoute('GET /users'); // OK
handleRoute('POST /items'); // OK
handleRoute('PATCH /users'); // Error: not a valid HttpMethod
handleRoute('GET users'); // Error: missing /
何时适合使用 String
interface Album {
// These genuinely can be any string:
artist: string; // "Miles Davis", "Queen", etc.
title: string; // Unlimited possibilities
// These should NOT be string:
recordingType: RecordingType; // Limited options
genre: Genre; // Limited options
}
抵抗压力的准则
1. “以后可能会增加更多值”
压力:“以后要是增加更多录制类型怎么办?”
**回应:**将它们加入联合类型。TypeScript 会指出所有需要处理这些类型的位置。
**行动:**从已知值开始,需要时再扩展。
2. “用户可能输入任何内容”
压力:“用户输入可能是任意字符串。”
**回应:**在边界处进行验证。在内部使用精确类型。
**行动:**解析/验证用户输入,然后在代码中使用精确类型。
3. “枚举太麻烦”
压力:“我不想为此创建枚举。”
**回应:**字符串字面量联合类型比枚举更轻量,精确度也不逊色。
**行动:**使用 type X = 'a' | 'b' | 'c',而不是 enum。
危险信号——停下来重新考虑
- 注释解释有效的字符串值
- 对字符串参数进行运行时验证
- 针对字符串的 switch 语句包含“绝不应该发生”的默认分支
- 字符串参数没有自动补全
- 字符串值拼写错误导致的 Bug
常见借口(都不成立)
| 借口 | 事实 |
|---|---|
| “它只是一个字符串” | 它是来自有限集合的字符串。应明确表达这一点。 |
| “我们之后再验证” | 在边界处验证,在内部使用类型。 |
| “值太多了” | 如果数量有限,就把它们列出来。TypeScript 能处理。 |
| “枚举很难看” | 改用字符串字面量联合类型。 |
快速参考
| 现有类型 | 改用 |
|---|---|
有 N 个有效值的 string | 'value1' | 'value2' | ... |
| 对象键参数 | keyof T |
| 带模式的字符串 | 模板字面量类型 |
| 作为字符串的日期 | Date |
| 作为字符串的 URL | 考虑使用品牌类型 |
迁移模式
// Step 1: Find strings with limited values
interface Config {
environment: string; // "dev", "staging", "prod"
}
// Step 2: Create the union type
type Environment = 'development' | 'staging' | 'production';
// Step 3: Use it
interface Config {
environment: Environment;
}
// Step 4: Fix all the errors (that's the point!)
const config: Config = { environment: 'dev' };
// ~~~~~ Did you mean 'development'?
总结
string 是字符串类型中的 any。
当值的范围有限时,就应明确表达这种限制。有限集合使用字符串字面量联合类型,对象键使用 keyof,模式使用模板字面量类型。这样代码会更安全,IDE 也能提供更多帮助。
参考
基于 Dan Vanderkam 的《Effective TypeScript》第 35 条:优先选择比 String 类型更精确的替代方案。
评论
支持 Markdown