TypeScript不允许React组件props的有效union类型

7jmck4yq  于 2023-05-19  发布在  TypeScript
关注(0)|答案(2)|浏览(135)

我创建了一个功能组件,看起来像这样:

type RootComponentProps = {
  propThatAlwaysExists: string;
};

type ComponentPropsV1 = {
  type: "v1";
  onlyOnV1: string;
  onlyOnV2?: never;
};

type ComponentPropsV2 = {
  type: "v2";
  onlyOnV1?: never;
  onlyOnV2: string;
};

type ComponentProps = RootComponentProps & (
  | ComponentPropsV1
  | ComponentPropsV2
);

export const Component: FC<ComponentProps> = ({
  propThatAlwaysExists,
  type,
  onlyOnV1,
  onlyOnV2,
}) => {
  // do typechecked things, no errors in component body
};

prop?: never类型是我学到的一种允许解构props的技巧,因为TypeScript禁止直接访问任何不***绝对保证***存在的属性,但解构是eslint等代码正确性工具的首选prop访问方法,在我看来,一般来说,代码可读性。
然后,我尝试将这个组件挂载到它的父组件中,父组件会传递大部分这些属性。我最初尝试明确地写出父项:

import { Component } from "./Component";

type RootParentComponentProps = {
  label: string;
  propThatAlwaysExists: string;
};

type ParentComponentPropsV1 = {
  type: "v1";
  onlyOnV1: string;
  onlyOnV2?: never;
};

type ParentComponentPropsV2 = {
  type: "v2";
  onlyOnV1?: never;
  onlyOnV2: string;
};

type ParentComponentProps = RootParentComponentProps & (
  | ParentComponentPropsV1
  | ParentComponentPropsV2
);

export const ParentComponent: FC<ParentComponentProps> = ({
  label,
  type,
  onlyOnV1,
  onlyOnV2,
}) => {
  // some effects, yadda yadda

  return (
    <div>
      <span>{label}</span>
      <Component
        propThatAlwaysExists={propThatAlwaysExists}
        type={type}
        onlyOnV1={onlyOnV1}
        onlyOnV2={onlyOnV2}
      />
    </div>
  );
};

但我得到了一个非常奇怪的错误:

Types of property 'onlyOnV1' are incompatible.
  Type 'string | undefined' is not assignable to type 'undefined'.
    Type 'string' is not assignable to type 'undefined'.ts(2322)

我想这可能是因为TS无法确定类型之间的相同排除,并且意识到我可以干燥一些代码,所以我将ParentComponent的类型定义更改为:

import { Component, ComponentProps } from "./Component";

type RootParentComponentProps = {
  label: string;
};

type ParentComponentProps = RootParentComponentProps & ComponentProps;

然而,错误仍然没有改变。
在这一点上,我认为问题可能是TypeScript实际上失去了属性排他性的上下文,和/或never的严格性实际上意味着never,所以我通过指定onlyOnV1onlyOnV2创建了一个不可能的props安排,当我创建的联合类型实际上说只有一个属性可以存在时。
为了解决这个问题,我尝试将Component的独占类型从

type ComponentPropsV1 = {
  type: "v1";
  onlyOnV1: string;
  onlyOnV2?: never;
};

type ComponentPropsV2 = {
  type: "v2";
  onlyOnV1?: never;
  onlyOnV2: string;
};

type ComponentPropsV1 = {
  type: "v1";
  onlyOnV1: string;
  onlyOnV2: undefined;
};

type ComponentPropsV2 = {
  type: "v2";
  onlyOnV1: undefined;
  onlyOnV2: string;
};

但是错误仍然没有改变,这对我来说就没有意义了。据我所知,这些都是真的:

  • TypeScript从ComponentProps的类型中知道onlyOnV1onlyOnV2具有反向互斥类型,其中如果一个是string,则另一个必须是undefined,反之亦然
  • ParentComponent传递到Component的类型***完全***是ComponentProps的类型

然而,不知何故,TypeScript发明了一种场景,其中独占联合类型从

{
  type: "v1";
  onlyOnV1: string;
  onlyOnV2: undefined;
} | {
  type: "v2";
  onlyOnV1: undefined;
  onlyOnV2: string;
}

{
  type: "v1" | "v2";
  onlyOnV1: string | undefined;
  onlyOnV2: string | undefined;
}

为了对ComponentProps进行类型检查,即使它实际上是ComponentProps
我是否错误地使用和/或设计了我的联合类型?
有没有什么方法可以在不牺牲ComponentParentComponent的显式性的情况下完成这项工作?

brgchamk

brgchamk1#

Typescript做了预期的事情;因为你破坏了 prop ,type可以是v1v2;这就是为什么你得到错误。有两种选择:选项1,不析构props并传递整个对象:

<Component {...props} />

选项2,显式检查type的值:

{type === 'v1' ? (
        <Component
          propThatAlwaysExists={'propThatAlwaysExists'}
          type={type}
          onlyOnV1={onlyOnV1}
          onlyOnV2={onlyOnV2}
        />
      ) : type === 'v2' ? (
        <Component
          propThatAlwaysExists={'propThatAlwaysExists'}
          type={type}
          onlyOnV1={onlyOnV1}
          onlyOnV2={onlyOnV2}
        />
      ) : null}

Playground

vwkv1x7d

vwkv1x7d2#

在尝试wonderflame's answer没有成功之后,我意识到我的“最小可重复示例”实际上过于简化了-这里没有描绘的是TypeScript内置Omit<T, U>类型的间隙使用,它允许您从对象中敲击键。
Omit负责类型扩展,因为当简单对象Map类型在复合类型上运行时,复合类型似乎会组合在一起。为了解决这个问题,我必须更明确地说明我的Component props,因为它们是从父对象传递过来的,***和***传播了用于Component的props,改变了这一点-

type ComponentPassthroughProps = Omit<ComponentProps, "strickenKey">

到这个

type ComponentPassthroughProps = RootComponentProps & Omit<
  | ComponentPropsV1
  | ComponentPropsV2,
  "strickenKey"
>

相关问题