TypeScript中如何在对象属性含已知前缀时推导正确类型?
TypeScript类型收窄问题:对象联合类型的类型守卫失效
问题场景
先定义以下类型:
type ColumnName< Prefix extends string, Period extends 'month' | 'week' > = `${Prefix}.${Period}`;
例如,ColumnName<'dimension_date', 'month'> 会得到 'dimension_date.month'。接着定义:
type Month<Prefix extends string> = ColumnName<Prefix, 'month'>; type Week<Prefix extends string> = ColumnName<Prefix, 'week'>;
先实现函数get,接受Month<Prefix>或Week<Prefix>类型的column,以及Prefix类型的prefix:
const get = <Prefix extends string>( column: Month<Prefix> | Week<Prefix>, prefix: Prefix ) => { const monthColumn: ColumnName<Prefix, 'month'> = `${prefix}.month`; if (column === monthColumn) { console.log(column); // 此时column类型为`${Prefix}.month`,符合预期 } };
该函数类型推导正常。但当参数改为对象时:
type MonthObject<Prefix extends string> = { [K in Month<Prefix>]: number }; type WeekObject<Prefix extends string> = { [K in Week<Prefix>]: number }; const getObj = <Prefix extends string>( object: MonthObject<Prefix> | WeekObject<Prefix>, prefix: Prefix ) => { const monthColumn: ColumnName<Prefix, 'month'> = `${prefix}.month`; if (monthColumn in object) { console.log(object); // 此时object仍为MonthObject<Prefix> | WeekObject<Prefix>,期望仅为MonthObject<Prefix> } }
问题:如何让TypeScript识别if语句内的object类型仅为MonthObject<Prefix>?
解决方案
自定义类型守卫
TypeScript内置的in操作符无法自动收窄泛型联合类型,因为monthColumn是基于泛型推导的动态字符串,而非静态字面量。我们可以编写自定义类型守卫来解决这个问题:
type ColumnName< Prefix extends string, Period extends 'month' | 'week' > = `${Prefix}.${Period}`; type Month<Prefix extends string> = ColumnName<Prefix, 'month'>; type Week<Prefix extends string> = ColumnName<Prefix, 'week'>; type MonthObject<Prefix extends string> = { [K in Month<Prefix>]: number }; type WeekObject<Prefix extends string> = { [K in Week<Prefix>]: number }; // 自定义类型守卫函数 function isMonthObject<Prefix extends string>( obj: MonthObject<Prefix> | WeekObject<Prefix>, monthColumn: ColumnName<Prefix, 'month'> ): obj is MonthObject<Prefix> { return monthColumn in obj; } const getObj = <Prefix extends string>( object: MonthObject<Prefix> | WeekObject<Prefix>, prefix: Prefix ) => { const monthColumn: ColumnName<Prefix, 'month'> = `${prefix}.month`; if (isMonthObject(object, monthColumn)) { console.log(object); // 此时object类型自动收窄为MonthObject<Prefix> } }
替代方案:类型断言
如果不需要复用类型判断逻辑,也可以直接在条件分支中使用类型断言:
const getObj = <Prefix extends string>( object: MonthObject<Prefix> | WeekObject<Prefix>, prefix: Prefix ) => { const monthColumn: ColumnName<Prefix, 'month'> = `${prefix}.month`; if (monthColumn in object) { console.log(object as MonthObject<Prefix>); // 手动断言为目标类型 } }
这种方式更简洁,但需要开发者自行保证断言的正确性,不如自定义类型守卫安全可靠。
内容的提问来源于stack exchange,提问作者Luuc van der Zee
相关产品推荐
相关产品推荐

