typescript 使用OR运算符过滤区分并集

g0czyy6m  于 2023-04-13  发布在  TypeScript
关注(0)|答案(2)|浏览(103)

请考虑以下类型定义:

type ClipBase = { duration: number; }

type AudioClip = ClipBase & {
    type: "audio"; 
    volume: number;
}

type VideoClip = ClipBase & {
    type: "video"; 
    height: number;
    width: number;
}

type MediaClip = ClipBase & {
    type: "media"; 
    url: string;
}

type Clip =  VideoClip | AudioClip | MediaClip;

我想在Clips数组上使用.filter并缩小类型。我使用以下助手函数,借用自here

export function isType<
  GenericType extends string,
  Union extends { type: GenericType },
  SpecificType extends GenericType,
>(val: SpecificType) {
  return (obj: Union): obj is Extract<Union, { type: SpecificType }> =>
    obj.type === val;
}

如果我只过滤一种类型的剪辑,这将很好地工作:

function getVideoClips(clips: Clip[]) {
  return clips.filter(isType("video")); // VideoClip[]
}

但是,如果我想得到区分并集的子集,TypeScript会返回原始类型Clip[],即使推断过滤器应该输出什么类型应该很简单。

function getAudioAndMediaClips(clips: Clip[]) {
  return clips.filter(clip => isType("audio")(clip) || isType("media")(clip)); // Clip[]
}

在Typescript Playground上的完整示例。
我能做些什么来帮助TypeScript成功地从一个区分的联合中推断出多个类型,而不必为每对类型编写特定的帮助函数?

92dk7w1h

92dk7w1h1#

类型 predicate 不是从函数实现中推断出来的,所以clip => isType("audio")(clip) || isType("media")(clip)不是类型保护函数。microsoft/TypeScript#38390中有一个建议,允许编译器自动将某些简单的函数实现视为类型保护函数,但现在它不是语言的一部分。除非实现了这样的功能,否则如果希望函数成为类型保护函数,则需要使用类型 predicate 注解其返回类型。
例如:

function getAudioAndMediaClips(clips: Clip[]) {
  return clips.filter((clip): clip is AudioClip | MediaClip =>
    isType("audio")(clip) || isType("media")(clip)); // (AudioClip | MediaClip)[]
}

在这里,你明确地告诉编译器回调返回clip is AudioClip | MediaClip,因此整个函数的返回类型是(AudioClip | MediaClip)[]
如果你不想手动完成,你需要写一个更通用的类型保护函数,它可以作用于单个type和多个type。例如,你可以将isType重写为variadic,并接受多个判别式值而不是一个:

function isType<T extends string | number | boolean | null | undefined>(
  ...types: T[]
) {
  return <U extends { type: any }>(
    obj: U
  ): obj is Extract<U, { type: T }> => 
    types.includes(obj[key]);
}

您可以像以前一样使用此命令:

function getVideoClips(clips: Clip[]) {
  return clips.filter(isType("video")); // VideoClip[]
}

但是你也可以传递一个额外的参数:

function getAudioAndMediaClips(clips: Clip[]) {
  return clips.filter(isType("audio", "media")); //  (AudioClip | MediaClip)[]
}

看起来不错!
Playground链接

ufj5ltwl

ufj5ltwl2#

您可以创建一个函数来合并is函数

function OR<T, A extends T, B extends T>(
  check1: (v: T) => v is A,
  check2: (v: T) => v is B
): (val: T) => val is (A | B) {
  return ((val: T) => check1(val) || check2(val)) as any
}

function getAudioAndMediaClips2(clips: Clip[]) {
  let result = clips.filter(
    // ^?
    // let result: (AudioClip | MediaClip)[]
    OR(
      isType("audio"),
      isType("media")
    )
  );
  return result;
}

相关问题