首页 > 解决方案 > 模型通用 Typescript 类型以强制多个道具具有相同的键集

问题描述

我正在尝试创建这个通用<FormBuilder />组件。我不想拿两套不同的道具,defaultValuesfields。使用泛型,我不想强​​制两个道具使用完全相同的键。

到目前为止,这是我想出的(这也可能有助于解释我想要实现的目标):

一个<FormBuilder />可以生成表单的组件,带有这样的 API。

<FormBuilder
  initialValues={{
    firstName: undefined,
    lastName: undefined,
  }}
  fields={{
    firstName: { type: 'text', label: 'First Name', required: true },
    lastName: { type: 'text', label: 'Last Name', required: true },
  }}
  onSubmit={value => {
    // Value is strongly typed, with keys 'firstName' and 'lastName'
  }}
/>

这很好,并且给了我强大的类型检查。如果我从其中一个道具中删除或添加一件事,Typescript 会给我一个警告。这将确保我在进行某些更改时永远不会忘记更新其他道具。此外,onSubmit()回调还将具有包含所使用的确切键的强类型。

使用截图

从“外部”来看,这个组件效果很好。 问题在于这是使用Record<>类型的事实,我无法在保持强类型的同时对其进行迭代。

下面你可以看到我对这个组件的实现。如您所见,我必须创建自己的iterator()函数来以更好的方式迭代数据,而且我还必须对一些值进行类型转换。

type FormProps<T, K extends keyof T> = {
  fields: Record<K, FieldItem>;
  initialValues: Record<K, string | undefined>;
  onSubmit: (value: Record<K, string | undefined>) => void;
};

export function FormBuilder<T, K extends keyof T>(props: FormProps<T, K>) {
  const [fields, setFields] = useState<Record<K, string | undefined>>(props.initialValues);
  
  function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    props.onSubmit(fields);
  }
  
  function handleChange(value: FieldOutput) {
    setFields({ ...fields, [value.name]: value.value });
  }
  
  return (
    <form onSubmit={handleSubmit}>
      {iterator(fields).map(({ value, key }) => (
        <React.Fragment key={key as string}>
          <FieldFactory valueKey={key as string} value={value} field={props.fields[key]} onChange={handleChange} />
        </React.Fragment>
      ))}
    </form>
  );
}

当我需要遍历onSubmit()处理程序中的值时,该组件的“外部”也会出现同样的问题。

迭代器实现如下。

export function iterator<Item>(item: Item) {
  const result: Array<{ key: keyof typeof item; value: Item[keyof typeof item] }> = [];

  let k: keyof Item;
  for (k in item) {
    const value = item[k];
    result.push({ value, key: k });
  }
  return result;
}

我在寻找什么

我愿意接受各种建议。一种在仍然保持类型的同时迭代对象的方法(从我从其他 SO 帖子中可以看到,这似乎很困难/不可能),或者可能是另一种类型/结构化数据的方法,并且仍然达到相同的效果。

我试过使用ArraySet但没有任何运气。

标签: reactjstypescriptgenericsdata-structurestypescript-generics

解决方案


推荐阅读