【问题标题】:How to generate React components dynamically using an array of Props in TypeScript?如何使用 TypeScript 中的 Props 数组动态生成 React 组件?
【发布时间】:2021-11-11 15:05:29
【问题描述】:

我一直在解决一个问题,即为组件创建抽象出一些逻辑,以减少大量重复。作为其中的一部分,我有一个通用的Builder 组件,我用它来根据提供的道具动态渲染组件。

问题在于我将元素定义为类似于以下内容:

type InputMap = typeof INPUTS
const INPUTS = {
  text: {
    component: TextInput,
    controlled: false
  },
  select: {
    component: Select
    controlled: true
  }
}

// Props for TextInput component
type TextProps = {
  onChange: (e: ChangeEvent<HTMLInputElement>) => void
  onBlur: (e: ChangeEvent<HTMLInputElement>) => void
}

// Props for Select component
type ElementProps = {
  onChange: (value: string) => void
  onBlur: () => void
}

我想以类似于以下的格式传递我的字段:

const fields = [
  {
    input: "text",
    props: {
      onChange: e => console.log(e.target.value)

    }
  },
  {
    input: "select",
    props: {
      onChange: value => console.log(value)
    }
  }
]

这是我想出的类型:

import { ComponentProps } from "react";

export type FieldConfig<T extends FieldValues, K extends keyof InputMap> = {
  input: K;
  props?: ComponentProps<InputMap[K]["Component"]>
};

但是在我的Builder 组件中,渲染组件时出现问题。

<div>
  { fields.map(({ input, props }) => {
    const { Component, controlled } = INPUTS[input]
    return <Component {...props} /> // ERROR HERE
  })}
</div>
const { input, props } = field

TypeScript 在这一点上给了我以下错误:

Types of property 'onBlur' are incompatible.
Type 'ChangeHandler' is not assignable to type '() => void' 

在这种情况下,我有什么办法可以将类型从联合缩小到该联合的特定实例?我正在尽力避免这里的任何类型断言。任何帮助将不胜感激!

【问题讨论】:

    标签: javascript reactjs typescript typescript-generics


    【解决方案1】:

    您可以使用公共字段接口和联合类型来定义应该如何处理您的表单结构。

    interface FieldDefinition<TType extends string, TElement extends HTMLElement> {
        input: TType
        placeholder?: string
        onChange?: React.ChangeEventHandler<TElement>
        onBlur?: React.ChangeEventHandler<TElement>
    }
    
    interface TextField extends FieldDefinition<'text', HTMLInputElement> {
    }
    
    interface SelectField extends FieldDefinition<'select', HTMLSelectElement> {
        options: Record<PropertyKey, any>
    }
    
    type FormField = TextField | SelectField
    
    const formFields: FormField[] = [
        {
            input: 'text',
            onChange: (event) => console.log(event.target.value)
        },
        {
            input: 'select',
            onChange: (event) => console.log(event.target.value),
            options: {
                foo: 'Foo',
                bar: 'Bar',
                baz: 'Baz'
            }
        }
    ]
    

    这使得它可以在将 JSX here's a link 返回到显示它用作组件的 TypeScript 游乐场时正确使用。

    这还有一个额外的好处,就是允许您定义特定类型的特定属性,这些属性可以像 options 对象一样定义为 select 输入。

    【讨论】:

    • 我认为这与我最初的方法有关,但我仍然遇到了假设组件之间的 onChange 和 onBlur(以及其他共享道具)的通用数据签名的地方。我可能会继续寻找更灵活的解决方案,但我真的很感激答案!如果没有其他问题,我会标记为解决方案。
    猜你喜欢
    • 1970-01-01
    • 2017-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-21
    • 1970-01-01
    • 2014-09-28
    • 2017-11-26
    相关资源
    最近更新 更多