mcpField
C
context-type-inference

context-type-inference

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

关于 context-type-inference

这项 TypeScript 技能讲解上下文如何影响类型推断,并说明提取值后如何保留精确类型。内容介绍类型注解、const 断言和 satisfies 的用法。

主要功能

  1. 01恢复丢失的类型上下文
  2. 02保留字面量类型
  3. 03维持元组类型精度
  4. 04用 satisfies 进行校验

适用场景

  1. 01修复提取值后出现的类型错误
  2. 02避免元组类型扩宽为数组
  3. 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 条:理解上下文在类型推断中的作用。

评论

支持 Markdown