mcpField
P
precise-string-types

precise-string-types

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

关于 precise-string-types

面向 TypeScript 开发者的编码技能,指导在字符串有效值有限时使用更精确的类型。内容涵盖字面量联合、keyof 和模板字面量类型,以改善安全性与表达清晰度。

主要功能

  1. 01字符串字面量联合指导
  2. 02用 keyof 表示对象键
  3. 03用模板字面量类型描述模式
  4. 04类型安全的 API 参数建议
  5. 05字符串类型迁移步骤

适用场景

  1. 01将参数限制为有效值
  2. 02将函数参数限制为对象键
  3. 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