【问题标题】:Conditional types for a dynamic React component in TypescriptTypescript 中动态 React 组件的条件类型
【发布时间】:2020-02-11 07:07:35
【问题描述】:

试图创建一个动态的 React 组件。根据所选组件有条件地传递正确 propType 的正确方法是什么。到目前为止,这是我在<SelectComponent {...props.props} /> 这一行上遇到的错误,因为道具与组件不匹配:

export interface SidebarProps {
    type: keyof typeof components,
    props: AddEditJobsProps | AddEditCustomersProps
}

const components = {
  job: AddEditJobs,
  customer: AddEditCustomers
};

export default function Sidebar(props: SidebarProps) {
  const { open, toggle } = useToggle();
  const SelectComponent = components[props.type];

  return (
    <RightSidebar open={open} toggleDrawer={toggle}>
      <SelectComponent {...props.props} />
    </RightSidebar>
  );
}

编辑:any 添加到 props 可修复错误,但 Typescript 将无法匹配所选的 type 与相应的 props 在类型检查期间这是我希望在这里完成的。

export interface SidebarProps {
    type: keyof typeof components,
    props: AddEditJobsProps | AddEditCustomersProps | any
}

【问题讨论】:

  • 所以SelectComponent 可以是AddEditJobsAddEditCustomers,具体取决于props.type 的值?
  • 你想要的行为是什么?你只是想摆脱错误吗?或者你想让 Typescript 真正推断出props.props 的类型?前者可能是可能的,但后者是不可能的,因为 SelectComponent 的实际类型在运行时之前是未知的,并且不能被 Typescript 推断。
  • @jered 好问题,我不关心运行时,我想要的行为是在编码期间从打字稿中获得良好的反馈,所以当我将“类型”传递给我的动态侧边栏组件时,我得到看看我还需要通过什么“道具”。

标签: javascript reactjs typescript


【解决方案1】:

如果您坚持以这种方式保持动态,那么您可能应该这样做。

正如我在评论中提到的,第一个问题是 TS 直到运行时才知道 props.type 的值是什么,因此它无法有效地提前推断出它应该是什么。为了解决这个问题,你需要一个简单的旧条件,它将显式呈现正确的组件:

export const Sidebar: React.FC<SidebarProps> = props => {
  const { open, toggle } = useToggle();

  let inner: React.ReactNode;
  if (props.type === "job") {
    inner = <AddEditJobs {...props.props} />;
  } else if (props.type === "customer") {
    inner = <AddEditCustomers {...props.props} />;
  }

  return (
    <RightSidebar open={open} toggleDrawer={toggle}>
      {inner}
    </RightSidebar>
  );
};

请注意,条件基本上断言该字段的值将是什么,这有助于 TS 推断形状的其余部分将是什么并改进代码时类型检查。

第二个问题是SidebarProps 的接口过于宽松。

你有这个:

export interface SidebarProps {
    type: keyof typeof components,
    props: AddEditJobsProps | AddEditCustomersProps
}

这基本上是在告诉 Typescript“该对象应该有一个与 components 中的一个键匹配的 type 字段,以及一个 props 字段是 AddEditJobsPropsAddEditCustomersProps”。但它没有指定如果type 等于"job"props 字段必须 匹配AddEditJobsProps。为此,您需要更明确地说:

export type SidebarProps =
  | {
      type: "job";
      props: AddEditJobsProps;
    }
  | { type: "customer"; props: AddEditCustomersProps };

这使用联合类型来确保SidebarProps 具有一个或另一个完整且有效的形状。

通过这些更改,不仅Sidebar 中的 TS 错误消失了,而且当您在另一个组件中渲染它时,您将获得预期的正确 TS 检查。如果type"job"props 属性不具有AddEditJobsProps 的预期形状,您将收到错误消息。在这个沙盒中亲自尝试一下:

https://codesandbox.io/s/youthful-fog-8lq41

【讨论】:

    【解决方案2】:

    我认为答案是您的“动态组件”模式让事情变得困难。人类和计算机都难以理解,导致代码难以阅读和维护,Typescript编译器无法准确检查。

    在这里使用的更好的模式是组件组合。使 &lt;Sidebar&gt; 更通用,这样它就不会关心它呈现什么子元素,而只处理打开/切换状态。

    export default const Sidebar: React.FC = ({children}) => {
      const { open, toggle } = useToggle();
    
      return (
        <RightSidebar open={open} toggleDrawer={toggle}>
          {children}
        </RightSidebar>
      );
    }
    

    然后,当你想渲染一个侧边栏时,你只需给它你想要它包装的孩子:

    <Sidebar>
      <AddEditJobs {...addEditJobsProps} />
    </Sidebar>
    
    // or
    
    <Sidebar>
      <AddEditCustomers {...addEditCustomersProps} />
    </Sidebar>
    

    您将获得准确而严格的类型检查(因为 TS 会知道组件和 props 的确切类型),并且您的代码结构将更具可读性和易于遵循。

    【讨论】:

    • 其实你提到的模式就是应用程序当前的设计方式。然而,该应用程序正在扩展,我发现需要一个可以根据上下文调用的动态侧边栏。这个想法是让 useSidebar 钩子可以从任何路线/屏幕调用,然后只需传递“类型”和“道具”,让侧边栏找出其余部分。我可以将它与 switch 语句一起使用,所以当你传递类型时,我只是在实际命名的组件之间切换,但这仍然会有完全相同的问题,这意味着当我传递类型时,我将无法将它与相应的道具匹配.
    • 我还编辑了问题以显示我如何绕过错误但仍然无法将类型与我在这里寻找的道具匹配。
    猜你喜欢
    • 2019-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-20
    • 2020-08-12
    • 2019-07-25
    • 2022-12-11
    • 2017-11-28
    相关资源
    最近更新 更多