【问题标题】:render dynamically react components with typescript使用打字稿动态渲染组件
【发布时间】:2021-05-26 09:33:43
【问题描述】:

对于一个项目,我想动态渲染不同的组件。 我将得到一个包含不同类型对象的数组,当我映射到该数组时,我想根据该对象的 prop 渲染一个组件。我可以使用 if/else || switch 语句,但是随着越来越多不同类型的对象,这些语句会很长,所以我想使用不同的方法,即使用 prop 作为对象的键,然后定义应该渲染的组件。

App.tsx

import instructions, { Instruction } from "./instructions";
import products, { Product } from "./products";

// COMPONENTS
interface ProductCompProps {
  content: Product;
}
const ProductComponent: React.FC<ProductCompProps> = ({ content }) => {
  return <h1>Product Component: {content.name}</h1>;
};

interface InstructionCompProps {
  content: Instruction;
}
const InstructionComponent: React.FC<InstructionCompProps> = ({ content }) => {
  return <h1>Product Component: {content.label}</h1>;
};
//

const datas = [...products, ...instructions];

interface Components {
  [key: string]: React.FC<ProductCompProps> | React.FC<InstructionCompProps>;
}
const components: Components = {
  ProductComponent,
  InstructionComponent,
};

const App: React.FC = () => {
  return (
    <div>
      {datas.map((data) => {
        const Component = components[data.component];
        return <Component content={data} />;
      })}
    </div>
  );
};

export default App;

products.ts

export interface Product {
  id: string;
  name: string;
  component: string;
}

const products: Product[] = [
  {
    id: "1",
    name: "Product name One",
    component: "ProductComponent",
  },
  {
    id: "2",
    name: "Product name Two",
    component: "ProductComponent",
  },
];

export default products;

instructions.ts

export interface Instruction {
  id: string;
  label: string;
  component: string;
}

const instructions: Instruction[] = [
  {
    id: "1",
    label: "Product name One",
    component: "InstructionComponent",
  },
  {
    id: "2",
    label: "Product name Two",
    component: "InstructionComponent",
  },
];

export default instructions;

所以这个自定义组件“组件”可以是 X 组件之一,每个组件都有自己的 props。

但是 Typescript 给我的 content prop 出现以下错误:

Type 'Product | Instruction' is not assignable to type 'Product & Instruction'.
  Type 'Product' is not assignable to type 'Product & Instruction'.

我的理解是,您只能索引具有某些类型的对象,例如字符串或数字。 在我设置字符串键的情况下:

interface Components {
  [key: string]: React.FC<ProductCompProps> | React.FC<InstructionCompProps>;
}

索引正在工作,但是我不明白为什么编译器会混合组件属性的类型...

我会非常感谢任何帮助。请理解,我对 typescript 很陌生,所以我仍然在为泛型和高级 typescript 的东西而苦恼。

【问题讨论】:

  • 如果每个对象的类型都是随机的,为什么不 any 输入?

标签: reactjs typescript


【解决方案1】:

输入您的 datas 数组:

const datas: (Product | Instruction)[] = [...products, ...instructions];

所以 Typescript 很困惑,因为它不知道 datas 的元素是否是对 Product 或 Instruction 的引用,因为你迭代它。这意味着 Component 被输入为 Product &amp; Instruction 并且当您通过不具有这两个属性的道具传递它时它会抱怨。

解决这个问题的最简单方法就是将该组件或数据键入为any,或者执行一些 if/else 将data 转换为预期类型,因为打字稿无法使用如果数组以任何方式生成,则能够在编译时弄清楚这一点。

下面的写法可能会更好:

Components.tsx

// These now take a subset of props without the component
type ProductProps = {
  id: string;
  name: string;
}
const ProductComponent: React.FC<ProductProps> = (props) => {
  return <h1>Product Component: {props.name}</h1>;
};

type InstructionProps {
  id: string;
  label: string;
}
const InstructionComponent: React.FC<InstructionProps> = (props) => {
  return <h1>Product Component: {props.label}</h1>;
};

export {
    ProductProps, 
    ProductComponent,
    InstructionProps,
    InstructionComponent
};

renderable.ts

/* A generic for a "renderable" object, with props + component in one object */
type Renderable<Props> = Props & {
    Component: React.FC<Props>
};

export { Renderable };

products.ts

type Product = Renderable<ProductProps>;

const products: Product[] = [
  {
    id: "1",
    name: "Product name One",
    Component: ProductComponent, // This is the actual component, not a string
  },
  {
    id: "2",
    name: "Product name Two",
    Component: ProductComponent,
  },
];

export default products;

export { Product };

instructions.ts

type Instruction = Renderable<InstructionProps>;

const instructions: Instruction[] = [
  {
    id: "1",
    label: "Product name One",
    Component: InstructionComponent,
  },
  {
    id: "2",
    label: "Product name Two",
    Component: InstructionComponent,
  },
];

export default instructions;

export { Instruction };

现在把它们放在一起......

App.tsx

const datas: (Product | Instruction)[] = [...products, ...instructions];

const App: React.FC = () => {
  return (
    <div>
      {datas.map((data) => {
        // We now directly take the props and component from the array rather than indexing it.
        const {
            Component, 
            ...passThroughProps
        } = data;
        return <Component {...(passThroughProps as any)} />;
      })}
    </div>
  );
};

export default App;

之所以可行,是因为我们现在已经告诉我们的应用程序组件一个通用结构,它可以用来渲染事物 - 我们的数据对象包含一个 Component 属性,而对象的其余部分包含该组件的属性值.

【讨论】:

  • 首先感谢您的回复!按照您所说的设置好所有内容后,我的数据数组中会出现错误:`Type '(Product | Instruction)[]' is notassignable to type 'Renderable []'。类型'产品 |指令'不可分配给类型'Renderable'。 `
  • 抱歉,打错了! const datas: Renderable&lt;ProductProps | InstructionProps&gt;[] = [...products, ...instructions]; 应改为 const datas: (Product | Instruction)[] = [...products, ...instructions];。我输入了 passThroughProps。
  • 这是一个 tsplayground 链接,我已经更新了答案。 pastebin.com/FFQAm6bf。 (由于字符限制,在 pastebin 中)
  • 再次感谢您的帮助。所以datas 实际上知道它的类型,这正是您在上面的评论中定义的。当我更正数据类型时,我基本上会得到与以前相同的错误:const Component: React.FC&lt;ProductCompProps&gt; | React.FC&lt;InstructionCompProps&gt; Type '{ id: string; name: string; } | { id: string; label: string; }' is not assignable to type 'IntrinsicAttributes &amp; ProductCompProps &amp; { children?: ReactNode; } &amp; InstructionCompProps'. Property 'label' is missing in type '{ id: string; name: string; }' but required in type 'InstructionCompProps'.
  • 嗨,威洛!这是一个十字路口类型 - 这是手册。 typescriptlang.org/docs/handbook/2/…
【解决方案2】:

我可以为所有感兴趣的人找到解决此问题的方法: 使用断言,您可以告诉编译器,props 不是 Product 和 Instruction 的交集。

const Component = components[data.component] as React.FC<{ content: Product | Instruction }>;

另外,你可以修改接口的类型:

export interface Product {
  id: string;
  name: string;
-  component: string;
+ component: "ProductComponent";
}

接口指令也是如此。

所以我们可以摆脱这个(丑陋的?)界面:

interface Components {
    [key: string]: FC<ProductProps> | FC<InstructionProps>;
}

- const components: Components = {
+ const components = {
  ProductComponent,
  InstructionComponent,
};

希望这将在未来对其他人有所帮助。 干杯

【讨论】:

    猜你喜欢
    • 2021-12-04
    • 2020-10-19
    • 2020-12-31
    • 2020-08-17
    • 2019-08-18
    • 2014-12-18
    • 1970-01-01
    • 2022-07-04
    • 2018-06-27
    相关资源
    最近更新 更多