TypeScript:为什么我的过滤方法无法缩小类型并消除数组中的 undefined 和 false

Joj*_*oji 13 javascript typescript

我从d.tsAPI 文件中得到了这种类型

type EventConfigurations = {
  Event1: {
    Enabled?: boolean;
  };

  Event2: {
    Enabled?: boolean;
  };

  Event3: {
    Enabled?: boolean;
  };
};
Run Code Online (Sandbox Code Playgroud)

现在我已经从该 API 调用的响应中返回了这些数据

const eventConfigurations: EventConfigurations = {
  Event1: { Enabled: true },

  Event2: { Enabled: false },

  Event3: { Enabled: true }
};
Run Code Online (Sandbox Code Playgroud)

现在我想将数据映射到


enum EventDesc {
  Event1 = "XXX",
  Event2 = "YYY",
  Event3 = "ZZZ"
}

type MyEvent = {
  eventDesc: EventDesc;
  topic: EventTopic;
};

const eventConfigurations: EventConfigurations = {
  Event1: { Enabled: true },
  Event2: { Enabled: false },
  Event3: { Enabled: true }
};

const events: MyEvent[] = (Object.keys(eventConfigurations) as Array<
  keyof EventConfigurations
>)

  .map(
    (eventType) =>
      eventConfigurations[eventType].Enabled &&
      ({
        eventDesc: EventDesc[eventType],
        topic: EventTopic[eventType]
      } as MyEvent)
  )

  .filter(Boolean);

Run Code Online (Sandbox Code Playgroud)

编译器抛出一个错误,指出可能events

(false | MyEvent | undefined)[]
Run Code Online (Sandbox Code Playgroud)

但我.filter(Boolean);在最后补充说,它应该消除它是or 的map可能性。falseundefined

这是现场演示https://codesandbox.io/s/thirsty-yonath-ttdhn?file=/src/index.ts

jca*_*alz 12

TypeScript 的标准库确实有一个签名Array.prototype.filter(),可以缩小数组的类型,但只有当编译器将传入的回调识别为类型保护函数(其返回类型是形式的类型谓词)paramName is SomeType时,它才会使用该签名。而且,不幸的是,编译器当前无法推断回调(如Booleanx => !!x)是类型保护;你必须这样注释它。

(有关使用某种控制流分析将某些函数解释为类型保护的功能请求,请参阅microsoft/TypeScript#16069 。)

这是一个带注释的回调:

const truthyFilter = <T>(x: T | false | undefined | null | "" | 0): x is T => !!x;
Run Code Online (Sandbox Code Playgroud)

或者,如果您确实想要,可以将其用作Boolean类型断言的实现,例如

const truthyFilter2 = Boolean as any as <T>(x: T | false | undefined | null | "" | 0) => x is T;
Run Code Online (Sandbox Code Playgroud)

这是一种通用的真实性检测器,尽管 TypeScript 并没有真正有一个好的方法来表示类型系统中所有可能的虚假值(NaN例如无法表示)。不管怎样,你可以看到它的实际效果,如下所示:

function fn(arr: Array<false | undefined | MyEvent>) {

  const item = arr[0];
  if (truthyFilter(item)) {
    item.eventDesc; // okay, no error
  }
Run Code Online (Sandbox Code Playgroud)

现在,如果您将它与filter()编译器一起使用,则会按预期缩小范围:

  const myEvents = arr.filter(truthyFilter)
  // const myEvents: MyEvent[]
Run Code Online (Sandbox Code Playgroud)

游乐场链接


And*_*ble 1

您需要使用类型保护

const filterUnwanted = (value: any): value is MyEvent => ('eventDesc' in value && 'topic' in value)
Run Code Online (Sandbox Code Playgroud)

或者不要Array#map跟随Array#filter,而是考虑Array#reduce

const events = (Object.keys(eventConfigurations) as Array<keyof EventConfigurations>)
  .reduce((acc, eventType) => (
    eventConfigurations[eventType].Enabled ? acc.concat([{
      eventDesc: EventDesc[eventType],
      topic: EventTopic[eventType],
    }]) : acc), [] as MyEvent[],
  );
Run Code Online (Sandbox Code Playgroud)