我想对数组应用一个过滤器链,并相应地缩小类型。数组元素属于具有一些共享属性的联合类型。
type TypeA = {name: string; id: string; createdAt: Date};
type TypeB = {name: string; id: string; metaData: Record<string, unknown>};
type TypeC = {id: string};
type TypeD = {name: string; createdAt: Date};
// etc.
type MyUnionType = TypeA | TypeB | TypeC | TypeD;
const hasName = (x: MyUnionType): x is MyUnionType & {name: string} => 'name' in x;
const hasId = (x: MyUnionType): x is MyUnionType & {id: string} => 'id' in x;
function foo(ary: MyUnionType[]) {
ary.filter(hasName)
.filter(hasId)
.map(x => x.name + x.id); // ❌ Error because it thinks property `id` does not exist
}
字符串
我想到了两个变通办法:
1.为我需要的组合写一个特定的过滤器:
function hasNameAndId(x: MyUnionType): x is MyUnionType & {name: string} {
return 'name' in x && 'id' in x;
}
型
这种解决方案是不可扩展的,因为它意味着为每个过滤器组合编写一个函数。
1.不要使用命名的过滤器函数,而是将过滤器与类型信息内联编写:
function foo(ary: MyUnionType[]) {
ary.filter((x): x is MyUnionType & {name: string} => 'name' in x)
.filter((x: MyUnionType & {name: string}): x is MyUnionType & {name: string; id: string} => 'id' in x)
.map(x => x.name + x.id);
}
型
这个解决方案很快就会变得混乱。
1条答案
按热度按时间h79rfbju1#
当你直接调用类型保护函数时,编译器会自动执行你所需要的收缩:
字符串
但是为了让
Array.filter()
作为一个类型保护函数工作,你需要传入一个与相关调用签名完全匹配的回调:型
不幸的是,当你调用
ary.filter(hasName).filter(hasId)
时,数组元素的类型是MyUnionType & { name: string }
,但你的回调函数接受的参数类型是MyUnionType
,这并不兼容,所以类型保护被跳过,你只得到了“正常”的filter()
行为,输出类型不变。我认为最直接的方法是让你的类型保护函数generic,这样对第二个
filter()
的调用就可以相应地示例化泛型类型参数。类似于这样:型
然后第二个
filter()
调用工作;T
将使用MyUnionType & {name: string}
示例化,并且返回类型是(MyUnionType & {name: string}) & {id: string})
的数组,如所需:型
Playground代码链接