我目前正在try 创建一个映射类型,该类型从给定的模型类型创建Angular 形状组类型.

我已经创建了一个映射类型,用于判断给定属性是否为数组,然后创建类型为FormArray<T>的映射属性.问题是枚举数组没有正确映射.

对于类型为MyEnum[]的属性,我得到的是已解析类型MyEnum.A[] | MyEnum.B[],因此不能将枚举值的不同值添加到数组中.

下面是一个带有相同问题的精简版本:


enum Environment{
  "EN" = "en",
  "US" = "us",
}

interface MyDto {
  environments: Environment[];
}

export type FormControlsOf<T> = {
  // Check if the property is an array
  [TPropertyKey in keyof T]-?: T[TPropertyKey] extends Array<infer TArray>
  // I check whether TArray is a record so i can decide whether this should be a FormControl or FormGroup in the real code
  ? TArray extends Record<any, any> ?
    // we ignore records for this example
    never 
    // We map to property to an array; here we use the simple array type but the real code would 
    // map this to FormArray<FormControl<TArray>>
    : TArray[]
  // we ignore non arrays for this example
  : never;  
}

const q: FormControlsOf<MyDto> = {
  environments: [Environment.EN, Environment.US]
  // The type of environments is actually (Environment.EN[] | Environment.US[])
};

// Error:  Type 'Environment[]' is not assignable to type 'Environment.EN[] | Environment.US[]'.
//  Type 'Environment[]' is not assignable to type 'Environment.EN[]'.
//    Type 'Environment' is not assignable to type 'Environment.EN'.
//      Type 'Environment.US' is not assignable to type 'Environment.EN'.(2322)
// input.tsx(8, 3): The expected type comes from property 'environments' which is declared here on type 'FormControlsOf<MyDto>'

来源:typescriptlang.org

如果我删除第13行TArray上的复选标记,environments的类型被正确检测为Environment[],但我需要该复选标记来确定该属性是FormArray<FormGroup>还是FormArray<FormControl>.

编辑:@仙火和@Robby Cornelissen是正确的,分布式条件类型是问题所在.

以下是Angular 形状组映射类型的完整代码:

/**
 * This mapped type allows to create types that represent the form group representation of that type.
 *
 * Example:
 * interface MyDto {
 *    prop1: string;
 *    prop2: string[];
 * }
 *
 * FormGroupOf<MyDto> will become:
 * {
 *    prop1: FormControl<string>;
 *    prop2: FormArray<FormControl<string>>;
 * }
 *
 * This allows to define fields that are fully typed.
 *
 * class TestClass {
 *
 *    public form: FormGroupOf<MyDto>;
 *
 *    constructor() : {
 *      this.form = new FormGroup({
 *        prop1: new FormControl(""),
 *        prop2: new FormArray<FormControl<string>>([]),
 *      })
 *    }
 * }
 *
 * Doing it this way allows code completion in both, the template and during the cunstruction of the formgroup.
 *
 * For complex property types this works recursively.
 * interface MyDto2 {
 *    prop1: {
 *      innerProp: string;
 *    };
 * }
 *
 * FormGroupOf<MyDto2> will become:
 * {
 *    prop1: FormGroup<{
 *      innerProp: FormControl<string|null>;
 *    }>;
 * }
 */
export type FormGroupOf<T> = FormGroup<FormControlsOf<T>>;

export type FormControlsOf<T> = {
  [TPropertyKey in keyof T]-?: T[TPropertyKey] extends
    | (infer TArray)[]
    | undefined
    ? [TArray] extends [string | number | boolean | undefined]
      ? FormArray<FormControl<TArray>>
      : FormArray<FormGroupOf<TArray>>
    : T[TPropertyKey] extends string | number | boolean | undefined
    ? FormControl<T[TPropertyKey] | null>
    : FormGroupOf<T[TPropertyKey]>;
};

推荐答案

您需要阻止条件类型的默认distributive行为:

export type FormControlsOf<T> = {
  [TPropertyKey in keyof T]-?: T[TPropertyKey] extends Array<infer TArray>
    ? [TArray] extends [Record<any, any>] // <- Prevent distributivity
      ? never 
      : TArray[]
    : never;  
}

Playground link

Typescript相关问答推荐

在TypScript手册中可以视为接口类型是什么意思?

忽略和K的定义

为什么typescript要把这个空合并转换成三元?

泛型和数组:为什么我最终使用了`泛型T []`而不是`泛型T []`?<><>

Webpack说你可能需要在Reactjs项目中增加一个加载器''

React重定向参数

为什么不能使用$EVENT接收字符串数组?

部分更新类型的类型相等

如何将父属性类型判断传播到子属性

不带其他属性的精确函数返回类型

是否有可能避免此泛型函数体中的类型断言?

我怎样才能正确地输入这个函数,使它在没有提供正确的参数时出错

与toast.error一起react 中的Async TUNK错误消息

类型脚本-在调用期间解析函数参数类型

在打字脚本中对可迭代对象进行可变压缩

必需的输入()参数仍然发出&没有初始值设定项&q;错误

如何在Reaction Native中关注屏幕时正确更新状态变量?

Typescript不允许在数组中使用联合类型

必须从注入上下文调用Angular ResolveFn-inject()

两个子组件共享控制权