为什么类型保护无法通过类型检查

Why is a type guard failing type-check

在 React 项目中,我使用类型保护来确保我传递的 prop 具有适当的类型。这似乎有效,因为 TypeScript 没有在我的 IDE 中引发任何错误。但是当 运行 作为命令进行类型检查时,编译器在该 prop 上失败。

我有一个正在渲染子组件的组件。该子组件需要接收具有自定义类型的道具,在枚举 AuthorizedType 中声明。为了确保传递的 prop 有效,我创建了一个类型保护 isAuthorizedType。如果类型无效,组件将不会呈现。

因为当类型保护失败时子组件没有呈现,TypeScript 没有在我的 IDE 中引发任何错误。但是当 运行 作为命令进行类型检查时,编译器会引发错误:

TS2322: Type 'string' is not assignable to type 'AuthorizedType'.

我想知道为什么在 IDE 中没有出现错误,但在 运行 类型检查时出现。我知道类型保护执行 runtime 检查,我想知道它的确切含义以及是否相关。

代码如下:

// ParentComponent.tsx

export enum AuthorizedType {
  First = 'first',
  Second = 'second',
}

function isAuthorizedType(value: any): value is AuthorizedType {
  return Object.values(AuthorizedType).indexOf(value) !== -1;
}

function ParentComponent({value}: {value: string}) {
  const isValidType = isAuthorizedType(value);

  if (!isValidType) {
    return null;
  }

  return (<ChildComponent value={value} />)
}

// ChildComponent.tsx
import {AuthorizedType} from '..';

function ChildComponent({value}: {value: AuthorizedType}) {
  switch(value) {
    case AuthorizedType.First:
      return <div>first</div>
    case AuthorizedType.Second:
      return <div>second</div>
  }
}

通过将类型保护移到子组件中,类型检查不再引发任何错误:

// ChildComponent.tsx
import {AuthorizedType} from '..';

function isAuthorizedType(value: any): value is AuthorizedType {
  return Object.values(AuthorizedType).indexOf(value) !== -1;
}

function ChildComponent({value}: {value: string}) {
  const isValidType = isAuthorizedType(value);

  if (!isValidType) {
    return null;
  }

  switch(value) {
    case AuthorizedType.First:
      return <div>first</div>
    case AuthorizedType.Second:
      return <div>second</div>
  }
}

我想知道为什么。

您的 ChildComponent 被标记为期望 AuthorizedTypeprops。但是,在 ParentComponent 中,您将 child 与 { prop: AuthorizedType } 称为 propsAuthorizedType extends string 并且不能与类型 { prop: AuthorizedType } 重叠,因此你会得到一个 TS2322 错误。

它在你的情况下消失的原因是当你将 isAuthorizedType 调用移动到你的 child 组件时,它正在尝试检查它接收到的 props 值(即 { prop: AuthorizedType }) 一个AuthorizedType(不可能是,因为object不扩展string),分配falseisValidType,并呈现 null

所以问题是由于在 ChildComponent 中期望 AuthorizedTypeprops 而实际上您应该期望 { prop: AuthorizedType }。当然,您可能希望使用更具描述性的名称来命名 属性,例如“value”:

function Child(props: { value: AuthorizedType }) {
    switch(prop.value) {
        case AuthorizedType.First:
            return <div>first</div>
        case AuthorizedType.Second:
            return <div>second</div>
    }
}

以下格式(使用 object destructuring)与上述格式相同,但可能更符合人体工程学:

interface ChildProps {
    value: AuthorizedType
}
function Child({ value }: ChildProps) {
    switch(value) {
        case AuthorizedType.First:
            return <div>first</div>
        case AuthorizedType.Second:
            return <div>second</div>
    }
}

然后您将调用 Child,例如(参见 jsx ... spread attributes):

function ParentComponent(props: ChildProps) {
    return isAuthorizedType(props.value)
        ? <Child {...props} />
        : null;
}

看完您的编辑后,我认为最好完全写出另一个答案。你的程序逻辑在我看来并没有缺陷,并且在验证之后,所以我相信你在 TypeScript 的控制流分析中发现了一个错误。

Here's a playground link 在等于或低于 4.3.5 的 TS 版本上 按预期 工作,但在 等于或高于 4.4.4 的版本。在错误版本 (>=4.4.4) 上,编译器将 ParentComponent 的 return 类型计算为 null,但实际上调用发出的 ParentComponent() 函数 returns "first".

我当然可能遗漏了配置标志或影响 CFA 工作方式的东西,或者版本 4 中可能有意更改。4.x,所以请检查您是否可以找到相关的错误 in GitHub. If not, I think you should file a report about this.