如何通过成员关系筛选元组并实现TypeScript类型收窄?
TypeScript中基于元组值筛选数组并实现精确类型推断
给定一个通过const断言定义的对象数组,其中每个对象包含嵌套的只读元组:
const foo = [ { id: 't1', values: ['a', 'b'] }, { id: 't2', values: ['a', 'c'] }, { id: 't3', values: ['b', 'c'] }, ] as const;
我们需要筛选出values包含'a'的元素,同时让返回结果的类型精确排除不符合条件的项(比如t3)。
原生filter的问题
直接使用filter会出现类型错误:只读元组的includes方法参数类型被限制为元组中的字面量类型,传入'a'会触发类型不匹配:
// 类型错误:类型'"a"'的参数不能赋给类型'"b"'的参数 var objsWithA = foo.filter(({ values }) => values.includes('a'));
如果用as any或as never绕过类型错误,虽然运行时结果正确,但返回类型仍然是原数组的完整类型,无法排除t3:
// 类型为readonly [{id: "t1", ...}, {id: "t2", ...}, {id: "t3", ...}] var objsWithA = foo.filter(({ values }) => values.includes('a' as never));
解决方案:自定义类型守卫
要实现精确的类型推断,需要编写类型守卫函数,让TypeScript能够识别出筛选后元素的具体类型:
针对当前场景的类型守卫
type FooElement = typeof foo[number]; // 类型守卫:判断对象的values元组是否包含指定值 function hasValue<T extends FooElement, V extends FooElement['values'][number]>( obj: T, value: V ): obj is T & { values: readonly [V, ...any[]] | readonly [...any[], V] } { return obj.values.includes(value as any); }
使用类型守卫筛选数组
const objsWithA = foo.filter(obj => hasValue(obj, 'a'));
此时objsWithA的类型会被精确推断为:
readonly [{ readonly id: "t1"; readonly values: readonly ["a", "b"]; }, { readonly id: "t2"; readonly values: readonly ["a", "c"]; }]
通用版本的类型守卫
如果需要复用在其他类似结构的数组上,可以写一个不依赖具体类型的通用版本:
function includesValue<T extends { values: readonly U[] }, U>( obj: T, value: U ): obj is T & { values: readonly [...any[], U, ...any[]] } { return obj.values.includes(value as any); }
原理说明
类型守卫通过obj is ...语法告诉TypeScript:当函数返回true时,传入的obj会被缩小为特定的子类型。结合const断言的字面量类型,TypeScript可以准确识别出哪些元素符合筛选条件,从而生成精确的返回类型。
内容的提问来源于stack exchange,提问作者lonewarrior556
相关产品推荐
相关产品推荐

