简体   繁体   English

TypeScript 在回调 function 中丢失类型知识(例如 .filter 和 .some 回调函数)

[英]TypeScript loses type knowledge inside of callback function (Ex. .filter and .some callback functions)

I'm seeing some very strange TypeScript behavior that I don't understand.我看到一些我不理解的非常奇怪的 TypeScript 行为。 Here is the scenario:这是场景:

I have three different interfaces that describe an event.我有三个不同的接口来描述一个事件。 They look like this他们看起来像这样

interface ReceiveMessageEvent {
  event: 'receive-message';
  payload: {
    action: 'receive-message';
    businessSMSId: number;
    message: {
      businessSMSId: number;
      from: string;
      to: string;
    };
  };
}

interface ReadMessageEvent {
  event: 'read-message';
  payload: {
    action: 'read-message';
    businessSMSId: number;
    customerNumber: string;
    inboxNumber: string;
  };
}

interface SetThreadsEvent {
  event: 'set-threads';
  threads: any[];
}

As you can see, all three interfaces have an event property that is a specific string, meaning there can only be three possible options, either 'receive-message', 'read-message', or 'set-threads'.如您所见,所有三个接口都有一个特定字符串的event属性,这意味着只能有三个可能的选项,“receive-message”、“read-message”或“set-threads”。 This is important because next I have a switch statement where the case is this event property:这很重要,因为接下来我有一个 switch 语句,情况就是这个事件属性:

switch (action.payload.event) {
  case 'receive-message':
    // ...
  case 'read-message':
    // ...
  case 'set-threads':
    // ...
}

So, since the case is the event property, I'd expect TypeScript to know the shape of the rest of the data depending on which case we're in. Now, is DOES do this correctly (as I'd expect), except in one specific scenario, which is where my confusion is coming from.因此,由于 case 是事件属性,我希望 TypeScript 知道数据的 rest 的形状,具体取决于我们所处的情况。现在,是否可以正确执行此操作(如我所料),除了在一种特定情况下,这就是我的困惑所在。 And that case is when I'm inside the callback function of the.filter method.这种情况是当我在 .filter 方法的回调 function 中时。

So for example:例如:

switch (action.payload.event) {
  case 'receive-message':
    // This is correct and has no errors
    const test = action.payload.payload.message.to

    // This is giving me a TS error, even though I'm accessing the exact same property
    // As in the line above
    myArray.filter((thread) => thread.property === action.payload.payload.message.to)
                                                                 ^^^^^^^^ TS error here
  case 'read-message':
    // ...
  case 'set-threads':
    // ...
}

The exact error from the example above is this:上面示例中的确切错误是这样的:

Property 'payload' does not exist on type 'ReceiveMessageEvent | ReadMessageEvent | SetThreadsEvent'.
  Property 'payload' does not exist on type 'SetThreadsEvent'.

It's as if Typescript loses its type knowledge once I enter the callback function.一旦我进入回调 function,就好像 Typescript 失去了它的类型知识。 The code does actually work exactly as expected, it's just TypeScript telling me there's an error even though there doesn't seem to be.该代码实际上确实按预期工作,它只是 TypeScript 告诉我有一个错误,即使似乎没有。

Lastly I'll note that I can do some casting like this, and then the errors disappear, although I'd prefer not to do this unless I absolutely have to):最后我会注意到我可以像这样进行一些铸造,然后错误消失,尽管我不希望这样做,除非我绝对必须这样做):

switch (action.payload.event) {
  case 'receive-message':
    myArray.filter((thread) => thread.property === (action.payload as ReceiveMessageEvent).payload.message.to)
  case 'read-message':
    // ...
  case 'set-threads':
    // ...
}

Is there a reason for this behavior, or this potentially a bug within TypeScript?这种行为是否有原因,或者这可能是 TypeScript 中的错误?

It's as if Typescript loses its type knowledge once I enter the callback function一旦我进入回调 function ,就好像 Typescript 失去了它的类型知识

Yep, that's it.是的,就是这样。 And that behavior is deliberate.而且这种行为是故意的。 Or at least, it's the best typescript can do.或者至少,这是 typescript 可以做到的最好的。

The issue is that typescript has no idea when the callback function will be called.问题是 typescript 不知道何时会调用回调 function。 You and i know that for .filter the callback will be called synchronously with nothing in between, but that's not the case for all callbacks.你和我都知道,对于.filter ,回调将被同步调用,中间没有任何内容,但并非所有回调都是如此。 For example, the callback in setTimeout will be called asynchronously.例如, setTimeout中的回调将被异步调用。

The type information doesn't identify whether it's synchronous or not, so typescript has to assume the worst case: asynchronous.类型信息无法识别是否同步,因此 typescript 必须假设最坏的情况:异步。 If a callback is called asynchronously, then any arbitrary code may have run before the callback, and so the check you did to narrow down the type may no longer be accurate.如果异步调用回调,则任何任意代码都可能在回调之前运行,因此您为缩小类型所做的检查可能不再准确。 Some code may have mutated the properties in the meantime.同时,某些代码可能已经改变了属性。

The simplest fix for this is usually to assign the value you care about to a const while you're still in non-callback code, and then refer to the const in the callback code.最简单的解决方法通常是在您仍在非回调代码中时将您关心的值分配给 const,然后在回调代码中引用 const。 With a const, typescript can then assume that it hasn't been reassigned.使用 const,typescript 可以假设它没有被重新分配。

case 'receive-message':
    const test = action.payload.payload.message.to
    myArray.filter((thread) => thread.property === test)

// OR:

case 'receive-message':
    const payload = action.payload.payload
    myArray.filter((thread) => thread.property === payload.message.to)

Playground link 游乐场链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM