typescript 如何将一个泛型类型传递给一个功能组件props,而这个功能组件props又为它的props扩展了另一个接口?

wbrvyc0a  于 2022-11-26  发布在  TypeScript
关注(0)|答案(1)|浏览(134)

解释
我正在使用react-hook-form中的FormProvider创建一个动态输入表单。

const formMethods = useForm<TSongFormData>();
return (
    <FormProvider {...formMethods}>
       <SongInputForm />
    </FormProvider>
)

SongInputForm组件中,我有我的实际表单,它将包含我为每个input elements创建的输入组件,如inputteaxtarea等...

const SongForm = () => {
  const { handleSubmit } = useFormContext<TSongFormData>();
  return (
        <form
          onSubmit={handleSubmit((data) => console.log("data", data))}
        >
          <InputElement label="Song Name" name="name" />
          <InputElement label="Author" name="author" />
          <input type="submit" />
        </form>
    );
  };
}

InputElement c组件将接收input元素本身的所有props,并且name属性将是表单数据的所有键的联合类型TSongFormDataKey

interface ITextInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  label?: string;
  name: TSongFormDataKey;
}

const InputElement = ({ name, label, ...restProps }: ITextInputProps) => {
  const { register } = useFormContext<TSongFormData>();
  return (
    <div>
      <label htmlFor={name}>{label}</label>
      <input id={name} {...restProps} {...register(name)} />
    </div>
  );
};

问题

到目前为止,我已经在InputElement组件中硬编码了TSongFormDataTSongFormDataKey类型。但是,我如何将TSongFormDataTSongFormDataKey类型作为泛型传递给InputElement组件,以便使其成为动态的,并在传递给它的属性类型上具有更大的灵活性呢?
我要找的是这样的东西:

interface ITextInputProps<T> extends React.InputHTMLAttributes<HTMLInputElement> {
  label?: string;
  name: T;
}

const InputElement = <T,K>({ name, label, ...restProps }: ITextInputProps<T>) => {
  const { register } = useFormContext<K>();
  return (
    <div>
      <label htmlFor={name}>{label}</label>
      <input id={name} {...restProps} {...register(name)} />
    </div>
  );
};

其中T将是TSongFormDataK将是TSongFormDataKey
我已经创建了一个codesandbox,如果有人想玩它:https://codesandbox.io/s/elastic-sanne-uz3ei8
我是打字脚本的新手,试图让我的头围绕泛型,但发现它很难。
任何帮助将不胜感激。谢谢

ztigrdn8

ztigrdn81#

可以将ITextInputProps类型和InputElement名称设置为泛型。对于name,我们可以使用useFormContext中的Path泛型类型。keyof T也可以使用,除了register需要Path<T>,而Path<T>将包含keyof T。而Path仍然具有未解析类型参数(例如T),则类型脚本将无法遵循该关系。
示例化组件时,必须显式指定参数:<InputElement<TSongFormData> label="Song Name" name="name" />
您还可以使用较新版本的TS中的示例化表达式为特定类型创建输入字段的专用版本:

const SongInputElement = InputElement<TSongFormData>

综合起来,我们得到:

interface ITextInputProps<T> extends React.InputHTMLAttributes<HTMLInputElement> {
  label?: string;
  name: Path<T>;
}

const InputElement = <T extends FieldValues>({ name, label, ...restProps }: ITextInputProps<T>) => {
  const { register } = useFormContext<T>();
  return (
    <div>
      <label htmlFor={name}>{label}</label>
      <input id={name} {...restProps} {...register(name)} />
    </div>
  );
};


const SongForm = () => {
  const { watch, handleSubmit } = useFormContext<TSongFormData>();

  return (
    <InputElement<TSongFormData> label="Author" name="author" />
  );
};

const SongFormV2 = () => {  
  const { watch, handleSubmit } = useFormContext<TSongFormData>();
  const SongInputElement = InputElement<TSongFormData>
  return (
    <SongInputElement label="Song Name" name="name" />
  )
};

Playground链接

相关问题