使用打字稿动态渲染组件

时间:2021-05-26 09:33:43

标签: reactjs typescript

对于一个项目,我想动态渲染不同的组件。 我会得到一个包含不同类型对象的数组,当我映射到该数组时,我想根据该对象的 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;

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

但是 Typescript 给了我以下内容道具的错误:

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>;
}

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

我将非常感谢您的任何帮助。请理解我对打字稿很陌生,所以我仍然在为泛型和高级打字稿的东西而苦苦挣扎。

2 个答案:

答案 0 :(得分:0)

输入您的 datas 数组:

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

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

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

下面这样写可能更好:

组件.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 属性,而对象的其余部分包含那个组件。

答案 1 :(得分:0)

我可以为每个感兴趣的人找出解决此问题的方法: 通过断言,您可以告诉编译器,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,
};

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