在TypeScript中,如何通过一系列过滤器缩小类型?

yhived7q  于 11个月前  发布在  TypeScript
关注(0)|答案(1)|浏览(120)

我想对数组应用一个过滤器链,并相应地缩小类型。数组元素属于具有一些共享属性的联合类型。

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);
}


这个解决方案很快就会变得混乱。

h79rfbju

h79rfbju1#

当你直接调用类型保护函数时,编译器会自动执行你所需要的收缩:

function foo(ary: MyUnionType[]) {
  ary.flatMap(x => hasName(x) && hasId(x) ? x.name + x.id : []) 
}

字符串
但是为了让Array.filter()作为一个类型保护函数工作,你需要传入一个与相关调用签名完全匹配的回调:

interface Array<T> {
  map<U>(cb: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
}


不幸的是,当你调用ary.filter(hasName).filter(hasId)时,数组元素的类型是MyUnionType & { name: string },但你的回调函数接受的参数类型是MyUnionType,这并不兼容,所以类型保护被跳过,你只得到了“正常”的filter()行为,输出类型不变。
我认为最直接的方法是让你的类型保护函数generic,这样对第二个filter()的调用就可以相应地示例化泛型类型参数。类似于这样:

const hasName = <T extends MyUnionType>(x: T): x is T & { name: string } => 'name' in x;
const hasId = <T extends MyUnionType>(x: T): x is T & { id: string } => 'id' in x;


然后第二个filter()调用工作; T将使用MyUnionType & {name: string}示例化,并且返回类型是(MyUnionType & {name: string}) & {id: string})的数组,如所需:

function foo(ary: MyUnionType[]) {
  ary.filter(hasName)
    .filter(hasId)
    .map(x => x.name + x.id); // okay
}


Playground代码链接

相关问题