Sanyanggae LogoSanyanggae

📱 移动与应用开发

TypeScript 条件类型与 infer:用小例子确认类型转换

通过例子了解条件类型、联合分配与模板字面量,区分编译期类型与执行时验证。

사냥개 (Sanyanggae)发布 更新 4分钟

本文是韩语原文的译本。 韩语原文 · 翻译审阅: 人工智能审阅

整理条件类型、infer 与分配、执行时验证的说明图
SANYANGGAE 制作的说明图:条件类型·infer 与分配·执行时验证。

TypeScript 里若相似的类型声明反复出现,就会想找从输入类型计算结果类型的方法。重点不在少了多少字,而在读者能否清楚看出类型之间的关系。本文用虚构配送通知服务介绍条件类型与 infer,并与确认外部数据实际是否正确的工作区分。

1. 条件类型是以类型为输入的分支

官方条件类型文件说明依 extends 条件选结果类型的语法。下面依是否为字符串类型,产生布尔字面量类型;它不是在执行时检查字符串的函数。

type IsText<T> = T extends string ? true : false;
type NameIsText = IsText<'parcel'>; // true
type CountIsText = IsText<3>;       // false

假设建立配送通知的函数接收字符串识别码,内部处理函数却接收数值识别码。使用条件类型前,应能说明为何区分两者。意义确实不同,独立名称便有帮助;只是表示差异,统一输入格式可能更易读。不要用类型技巧掩盖数据设计的模糊。

评估类型时,成对记下预期输入与结果。把字符串、数值、联合及非预期输入分成小例子,可降低阅读复杂声明的负担。也确认注释中的结果与编辑器推断是否一致。类型名称看似自然,实际计算仍可能不同。

2. 联合分配会改变数组的含义

类型参数直接位于条件左侧时,条件会分配到联合每个成员。若想阻止分配,把整个联合视为一个整体,可比较用方括号包住的形式。

type SeparateLists<T> = T extends unknown ? T[] : never;
type Together<T> = [T] extends [unknown] ? T[] : never;
type A = SeparateLists<string | number>; // string[] | number[]
type B = Together<string | number>;      // (string | number)[]
const a: A = ['a', 'b'];
const b: B = ['a', 1];

第一个是只有字符串的数组,或只有数值的数组;第二个允许同一数组混合两者。整份配送清单只能放一类代码,还是各项目可用不同类型,会影响适合的结果。画面上都有方括号,两个数组的契约却不同。

实务出问题时,不要一次读完复杂工具类型,先逐一放入成员。比较输入字符串、数值以及两者联合的结果。分配是否出于本意清楚了,也就能说明增加方括号的理由。评估记录不只留最终类型,也留下希望允许的实际值例子。

3. infer 为结构中的类型命名并取出

infer 用于替条件类型中比对到的部分结构命名。以下是只拆开一层 Promise 的小类型。

type OneResult<T> = T extends Promise<infer Value> ? Value : T;
type Parcel = OneResult<Promise<{ tracking: string }>>;
type ParcelList = OneResult<Promise<string[]>>;

程序不会执行或等候异步作业,只在编译阶段表达结果形状。若取 Resolved 一类的函数名称,可能让人以为实际值已抵达,所以要连同使用位置的上下文阅读。执行中的等待仍由异步程序处理。

若需要递归等候后的结果类型,别持续扩充自制单层例子,先看标准工具类型 Awaited。教学例子的简单与共享工具需求不同。团队引入新工具时,宜记下哪些输入与标准类型结果不同,以及为何需要此差异。

4. 用模板字面量连接事件名称

模板字面量类型能把字符串字面量的组合表示为类型。配送服务可以用领域和操作组合的事件名称作例子。

type Scope = 'parcel' | 'address';
type Action = 'created' | 'updated';
type EventName = `${Scope}:${Action}`;
const event: EventName = 'parcel:created';

编译期能发现名称拼错,但所有可组合事件是否真的符合服务规则,是另一回事。例如只支持地址删除事件,简单的领域与操作乘积可能无法准确表达规则。此时先考虑把允许事件与 payload 明确对应的设计。

事件越多,读者能否方便找到实际名称越重要。与其接续多层自动组合规则,也可让传送至外部系统的名称集中在一份文件。比较加入抽象前后,新成员会怎样发现错误事件。类型的精细度和易用性需要一起评估。

5. 外部 JSON 必须在执行中检查

只替 API 响应标示 User 或 Parcel 类型,不保证实际响应具备该结构。来看把外部值当 unknown 接收,再检查所需字段的简单形式。

function hasTracking(value: unknown): value is { tracking: string } {
  return typeof value === 'object' && value !== null
    && 'tracking' in value && typeof value.tracking === 'string';
}
const incoming: unknown = { tracking: 'DEMO-001' };
if (hasTracking(incoming)) console.log(incoming.tracking);

这只确认 tracking 字段是不是字符串,不检查单号是否真的存在,也不检查用户是否有权查看该配送。类型收窄、业务规则与权限检查是不同责任。官方 Narrowing 文件介绍通过条件判断收窄类型信息的过程。

6. 选择类型工具的小型评估顺序

先写两个应接受的实际值、两个应拒绝的值,再尝试以普通联合和明确的对象声明表达意思。仍有重复关系时再用条件类型或 infer,引入技巧的理由就清楚了。若阅读声明的时间大增,也可只抽出共享部分,其余保留普通类型。

最后分开执行类型检查与执行时检查。确认编译器拒绝错误事件名称,与确认服务器漏传字段时程序仍能解释错误,是互补的工作。先验证小例子,再连到实际输入边界,类型转换才有助于安全的应用程序设计。

依据与参考资料

本文依据公开资料进行说明。标注某机构为来源,不代表该机构审阅或认证了本文。

运营者介绍与联系方式

同主题文章