0

我创建了一个表单生成器,它接受配置并呈现表单。基本结构如下所示:

const sampleConfig = {
  meta: 'someMetaData',
  fields: [
    {
      controlName: 'a',
      type: 'text'
    },
    {
      controlName: 'b',
      type: 'select'
    },
    {
      controlName: 'c',
      type: 'subGroup',
      fields: [
        {
          controlName: 'cA',
          type: 'number'
        }
      ]
    }
  ]
}

此配置将创建 3 个表单字段:一个文本字段、一个选择字段和一个带有数字字段的子组。您可以看到每个字段可以是一个控件,也可以有一个带有自己的字段数组的子组。

我已经强输入了配置,对于这个例子,我们假设它是这样的:

interface Field {
  controlName: string;
  type: 'number' | 'select' | 'text' | 'subGroup'
  fields?: Field[]
}

interface FormConfig {
  meta: string
  fields: Field[]
}

但我想启用一个泛型来验证controlName对应模型的 s 。

对于此示例,模型将如下所示:

interface SampleModel {
  a: string;
  b: string;
  c: {
    cA: number;
  }
}

理想情况下,我可以像这样键入配置:

const sampleConfig: FormConfig<SampleModel> = { ... }

如果我的控件名称与键不对齐,它是否会引发编译错误。我可以很容易地确保 controlName 与模型键之一匹配,如下所示:

interface Field<M, K extends keyof M> {
  name: K;
  type: 'select' | 'text' | 'number'
}

interface SubGroupField<M, K extends keyof M> extends Omit<Field<M, K>, 'type'> {
  type: 'subGroup',
  fields?: Field<M[K], keyof M[K]>[];
}

interface FormConfig<M, K extends keyof M> {
  meta: 'string';
  fields: Field<M, K>[] | SubGroupField<M[K], keyof M[K]>[];
}

虽然这不会将配置与模型紧密绑定,但它只是确保控件名称仅限于模型键名称,每个字段都可以使用相同的 controlName,只要它匹配其中一个,Typescript 就不会抱怨模型的关键名称。

fields如果我将属性设为对象而不是数组并使用controlNames 作为每个字段对象的键,这将很容易实现。不幸的是,这个库已经存在了一段时间,如果可能的话,我需要避免重大的重大变化。

显然,这可能对所有类型的场景都有用,因此,如果您对问题有更好的标题,我很乐意更改它以帮助其他人更轻松地找到它。我非常彻底地搜索了类似的问题,但找不到任何问题,所以如果已经回答了这个问题,我也会更新以指向那里。

对此的任何指导将不胜感激。谢谢!

已编辑:包括来自@Linda Paise 的建议,将“子组”隔离为拥有类型,强制只有这种类型才能具有字段属性。

4

1 回答 1

1

我假设您想保留模型的确切结构。这意味着aandb是简单的字段,但c必须是'subGroup'with 字段cA

fields如果我将属性设置为对象而不是数组并将其controlNames用作每个字段对象的键,这将很容易实现。不幸的是,这个库已经存在了一段时间,如果可能的话,我需要避免重大的重大变化。

基本上我们要做的是将对象映射到您描述的字段的键控对象。然后将其转换为一个数组,该数组是该对象所有值的并集。

type Values<T> = T[keyof T][];

与键控对象相比,数组确实有一些缺点,因为我们不会在缺少字段或重复字段时出现错误。但是我们确实在无效字段上遇到错误。

我们可以做得比仅仅fields作为 a 的可选属性更好Field。我们可以说,如果键是object模型的属性,那么该字段必须'subGroup'其字段与该对象的属性匹配的字段(请注意,这将适用于数组,在这种情况下可能不是您想要的)。如果键是模型的原始值,则它type是其中之一,'number' | 'select' | 'text'并且没有fields属性。

我们还说,controlName必须是关键,而不仅仅是任何string。这使我们即使在扁平化版本中也能实现严格的输入。

综上所述,我们得到:

type Values<T> = T[keyof T][];

type MapFields<Model> = {
  [K in keyof Model]: Model[K] extends object ? {
    controlName: K;
    type: 'subGroup';
    fields: Values<MapFields<Model[K]>>;
  } : {
    controlName: K;
    type: 'number' | 'select' | 'text';
  }
}

type Config<Model> = {
  meta?: any;
  fields: Values<MapFields<Model>>;
}

打字稿游乐场链接

于 2021-02-24T22:59:36.840 回答