TypeScript 丢失了回调函数内部的类型知识(例如 .filter 和 .some 回调函数)

jer*_*rfp 3 typescript react-redux

我看到一些我不理解的非常奇怪的 TypeScript 行为。这是场景:

我有三个不同的界面来描述一个事件。他们看起来像这样

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[];
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,所有三个接口都有event一个特定字符串的属性,这意味着只能有三个可能的选项,“receive-message”、“read-message”或“set-threads”。这很重要,因为接下来我有一个 switch 语句,其中的情况是此事件属性:

switch (action.payload.event) {
  case 'receive-message':
    // ...
  case 'read-message':
    // ...
  case 'set-threads':
    // ...
}
Run Code Online (Sandbox Code Playgroud)

因此,由于 case 是事件属性,我希望 TypeScript 能够根据我们所处的情况知道其余数据的形状。现在,是否正确执行此操作(正如我所期望的那样),除了在一个特定的场景中,这就是我的困惑所在。这种情况是当我在 .filter 方法的回调函数内部时。

例如:

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':
    // ...
}
Run Code Online (Sandbox Code Playgroud)

上面例子中的确切错误是这样的:

Property 'payload' does not exist on type 'ReceiveMessageEvent | ReadMessageEvent | SetThreadsEvent'.
  Property 'payload' does not exist on type 'SetThreadsEvent'.
Run Code Online (Sandbox Code Playgroud)

一旦我进入回调函数,Typescript 就好像失去了它的类型知识。该代码实际上确实按照预期工作,只是 TypeScript 告诉我有错误,尽管似乎没有错误。

最后我会注意到,我可以像这样进行一些转换,然后错误就会消失,尽管我不想这样做,除非我绝对必须这样做):

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':
    // ...
}
Run Code Online (Sandbox Code Playgroud)

这种行为是否有原因,或者这可能是 TypeScript 中的错误?

Nic*_*wer 8

一旦我进入回调函数,Typescript 就好像失去了它的类型知识

是的,就是这样。而这种行为是故意的。或者至少,这是打字稿能做到的最好的。

问题是打字稿不知道回调函数何时被调用。你和我都知道.filter回调将被同步调用,中间没有任何内容,但并非所有回调都是如此。例如,in中的回调setTimeout将被异步调用。

类型信息无法识别它是否是同步的,因此打字稿必须假设最坏的情况:异步。如果异步调用回调,则任何任意代码都可能在回调之前运行,因此您为缩小类型范围所做的检查可能不再准确。一些代码可能同时改变了属性。

最简单的解决方法通常是在非回调代码中将您关心的值分配给 const,然后在回调代码中引用该 const。使用 const,打字稿可以假设它尚未被重新分配。

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)
Run Code Online (Sandbox Code Playgroud)

游乐场链接

  • 链接到 [ms/TS#9998](https://github.com/microsoft/TypeScript/issues/9998) 可能有用。@jerfp 此类信息当前无法在类型系统中表示;它与该函数是否是本机无关。[ms/TS#11498](https://github.com/microsoft/TypeScript/issues/11498) 有一个功能建议可以实现这一点,如果实现了,则指向“filter()”的调用签名可以将其回调注释为“立即”。但现在它不再是语言的一部分。 (2认同)