关于 context-type-inference
这项 TypeScript 技能讲解上下文如何影响类型推断,并说明提取值后如何保留精确类型。内容介绍类型注解、const 断言和 satisfies 的用法。
主要功能
- 01恢复丢失的类型上下文
- 02保留字面量类型
- 03维持元组类型精度
- 04用 satisfies 进行校验
适用场景
- 01修复提取值后出现的类型错误
- 02避免元组类型扩宽为数组
- 03为脱离上下文的回调添加类型
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill context-type-inference安装后在 Claude Code 中输入/context-type-inference确认已加载。
详细介绍
理解上下文在类型推断中的作用
概述
将值与其上下文分离可能导致类型错误。
TypeScript 会根据值以及值的使用位置来推断类型。将值提取到变量中时,上下文会丢失,这可能导致出乎意料的类型错误。
何时使用此技能
- 将值提取到常量后导致类型错误
- 回调参数的类型不正确
- 元组类型变成数组类型
- 字符串字面量变成普通字符串
铁律
When extraction breaks types, restore context with:
annotations, const assertions, or satisfies.
请记住:
- TypeScript 会使用函数参数提供的上下文
- 提取出的值会丢失该上下文
as const可以防止类型扩大satisfies可在进行校验的同时保留上下文
检测:上下文丢失
type Language = 'JavaScript' | 'TypeScript' | 'Python';
function setLanguage(language: Language) { /* ... */ }
setLanguage('JavaScript'); // OK - context from parameter
let language = 'JavaScript'; // Inferred as string (widened)
setLanguage(language);
// ~~~~~~~~
// Argument of type 'string' is not assignable to 'Language'
提取值时,其类型扩大成了 string。
解决方案 1:类型注解
let language: Language = 'JavaScript';
setLanguage(language); // OK
类型注解提供了上下文。
解决方案 2:const 声明
const language = 'JavaScript';
// ^? const language: "JavaScript"
setLanguage(language); // OK
const 变量会获得更窄的类型(字面量类型)。
解决方案 3:const 断言
let language = 'JavaScript' as const;
// ^? let language: "JavaScript"
setLanguage(language); // OK
即使使用 let,as const 也能防止类型扩大。
元组类型:常见情况
function panTo(where: [number, number]) { /* ... */ }
panTo([10, 20]); // OK - inferred as tuple from context
const loc = [10, 20];
// ^? const loc: number[] (array, not tuple!)
panTo(loc);
// ~~~
// Argument of type 'number[]' is not assignable to '[number, number]'
使用类型注解修复
const loc: [number, number] = [10, 20];
panTo(loc); // OK
使用 const 断言修复
const loc = [10, 20] as const;
// ^? const loc: readonly [10, 20]
注意:as const 会使其变为 readonly。如果函数需要可变元组,这种方式就行不通:
panTo(loc);
// ~~~
// 'readonly [10, 20]' is not assignable to '[number, number]'
如果函数不会修改输入,就应接受 readonly [number, number]。
对象属性
type Point = [number, number];
const capitals1 = { ny: [-73.7562, 42.6526], ca: [-121.4944, 38.5816] };
// ^? { ny: number[]; ca: number[]; } (arrays, not tuples)
const capitals2 = {
ny: [-73.7562, 42.6526],
ca: [-121.4944, 38.5816],
} satisfies Record<string, Point>;
// ^? { ny: [number, number]; ca: [number, number]; } (tuples!)
satisfies 可在保留精确类型的同时提供上下文。
satisfies 与类型注解的对比
const capitals3: Record<string, Point> = capitals2;
capitals3.pr; // No error! Property 'pr' returns Point (but undefined at runtime)
// ^? Point
capitals2.pr;
// ~~
// Property 'pr' does not exist
satisfies 会保留精确的键;类型注解则不会。
回调上下文
TypeScript 会根据上下文推断回调参数的类型:
// Types inferred from app.get declaration
app.get('/health', (request, response) => {
// ^? Request<...>
// ^? Response<...>
response.send('OK');
});
// Don't add redundant annotations:
app.get('/health', (request: express.Request, response: express.Response) => {
// Redundant and verbose
});
提取出的回调
// Context lost when callback is extracted
const handler = (request, response) => {
// ^? any ^? any
response.send('OK');
};
// Fix: Add types
const handler: express.RequestHandler = (request, response) => {
// ^? Request ^? Response
response.send('OK');
};
对象方法
const config = {
language: 'JavaScript' as const,
// ^? "JavaScript" (not string)
};
setLanguage(config.language); // OK
如果不使用 as const,config.language 的类型就会是 string。
深层 const 断言
const obj = {
settings: {
language: 'JavaScript',
version: 4,
}
} as const;
// ^? { readonly settings: { readonly language: "JavaScript"; readonly version: 4; } }
as const 会深层生效——所有内容都会变为只读并具有字面量类型。
抵御压力的规范
1. “直接添加类型断言就行”
压力:“我会用 as Language 来修复。”
**回应:**类型断言会绕过安全检查。请改用 const 或 satisfies。
**行动:**使用 as const 来收窄字面量类型,而不是使用 as Type。
2. “直接写时明明能用”
压力:“为什么提取出来后就不行了?”
**回应:**上下文丢失了。请显式恢复上下文。
**行动:**添加类型注解、使用 const,或使用 satisfies。
危险信号——停止并重新考虑
- 将值提取到变量后出现类型错误
- 回调参数的类型被推断为
any - 元组变成数组
- 字符串字面量变成
string
常见借口(均不成立)
| 借口 | 事实 |
|---|---|
| “值是一样的” | 值相同,但上下文不同,推断出的类型也不同 |
| “TypeScript 应该能知道” | TypeScript 需要上下文才能推断出精确类型 |
“我用 as 修复就行” | 请使用 as const 或 satisfies,而不是类型断言 |
快速参考
type Lang = 'JS' | 'TS';
declare function setLang(l: Lang): void;
// DON'T: Lose context
let lang = 'JS'; // string
setLang(lang); // Error
// DO: Annotation
let lang: Lang = 'JS';
// DO: const
const lang = 'JS'; // "JS"
// DO: const assertion
let lang = 'JS' as const; // "JS"
// For objects: satisfies
const cfg = { lang: 'JS' } satisfies Record<string, Lang>;
总结
上下文对类型推断很重要。
提取值时,类型上下文可能会丢失。请使用类型注解、const 声明、as const 断言或 satisfies 来恢复上下文。请根据具体情况选择合适的工具。
参考资料
基于 Dan Vanderkam 的《Effective TypeScript》第 24 条:理解上下文在类型推断中的作用。