我想知道在类型脚本和映射类型中有什么可用来创建将应用初始类型中定义的只读和可选属性的映射类型.举个例子会更容易解释.

我已经做了一个尽可能简单的例子,从定义为

type PropertyAttributes = {
  readonly optional?: boolean;
  readonly readonly?: boolean;
  readonly value: string; //this would be something else but to simplify example like this
};

type ObjectDefinition = Record<string, PropertyAttributes>

在某种程度上,这是一个模板,我想用它来创建一个类型.目前,我有这个解决方案,通过创建4个不同的类型和更少的组合它们,工作得相当好.

//Four types corresponding to 4 types of objects
type ReadonlyRequired<O extends ObjectDefinition> = {
  +readonly [K in keyof O as O[K]["readonly"] extends true
    ? O[K]["optional"] extends true
      ? never
      : K
    : K]: O[K];
};

type ReadonlyOptional<O extends ObjectDefinition> = {
  +readonly [K in keyof O as O[K]["readonly"] extends true
    ? O[K]["optional"] extends true
      ? K
      : never
    : never]?: O[K] | undefined;
};

type MutableRequired<O extends ObjectDefinition> = {
  -readonly [K in keyof O as O[K]["readonly"] extends true
    ? never
    : O[K]["optional"] extends true
    ? never
    : K]: O[K];
};

type MutableOptional<O extends ObjectDefinition> = {
  -readonly [K in keyof O as O[K]["readonly"] extends true
    ? never
    : O[K]["optional"] extends true
    ? K
    : never]?: O[K] | undefined;
};
//Expand Util
export type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;

//Put them all together
type Combine<O extends ObjectDefinition> =  Expand<
  ReadonlyRequired<O> &
    ReadonlyOptional<O> &
    MutableRequired<O> &
    MutableOptional<O>
>;

最后是一个类型,我认为它与问题的核心无关,但它将结果类型映射为获取对象

//simple maps the Object to value preserving keys since homomorphic as I understand
type MapToValue<O extends ObjectDefinition> = {[K in keyof O]: NonNullable<O[K]>["value"]} //need NonNullable for optional types or infered as unknown

type ToObj<O extends ObjectDefinition>=MapToValue<Combine<O>>

这很好用,有点长,唯一的问题是顺序不会被保留,因为我们必须拆分它.我只是想知道有没有人能想出不同的方法?

或者你能想到的任何微小的改进都将是令人惊叹的.

这里是playground 链接typescript playground

谢谢

推荐答案

一种方法是按如下方式编写ToObj:

type ToObj<T extends Record<keyof T, PropertyAttributes>> = {
  -readonly [K in keyof T]-?: (x:
    MaybeOptional<
      MaybeReadonly<
        Record<K, T[K]['value']
        >, T[K]['readonly']
      >, T[K]['optional']
    >) => void }[keyof T] extends ((x: infer I) => void) ?
  { [K in keyof I]: I[K] } : never;

其中MaybeOptional<T, B>MaybeReadonly<T, B>是实用程序类型,它们根据布尔式B是否为true而将PartialReadonly应用于对象类型T:

type MaybeOptional<T, B extends boolean | undefined> =
  B extends true ? Partial<T> : T

type MaybeReadonly<T, B extends boolean | undefined> =
  B extends true ? Readonly<T> : T;

ToObj<T>在这里的工作方式是使用mapped type依次遍历T的每个属性,并创建一个具有optionalreadonly位的单个属性对象.在这里,让我们把这一部分分解成它自己的东西:

type ToObjStepOne<T extends Record<keyof T, PropertyAttributes>> = {
  -readonly [K in keyof T]-?:
  MaybeOptional<
    MaybeReadonly<
      Record<K, T[K]['value']
      >, T[K]['readonly']
    >, T[K]['optional']
  > }

您可以看到这在输入类型上的行为:

type S1 = ToObjStepOne<{
  readonlyRequired: { readonly: true; value: "someValue" };
  readonlyOptional: { readonly: true; optional: true; value: "someValue" };
  mutableRequired: { value: "someValue" };
  mutableOptional: { optional: true; value: "someValue" };
}>;
/* type S1 = {
    readonlyRequired: Readonly<Record<"readonlyRequired", "someValue">>;
    readonlyOptional: Partial<Readonly<Record<"readonlyOptional", "someValue">>>;
    mutableRequired: Record<"mutableRequired", "someValue">;
    mutableOptional: Partial<Record<"mutableOptional", "someValue">>;
} */

然后我们使用从Transform union type to intersection type开始的相同技术,我们将每个属性放在contravariant的位置,这样我们就可以推断出单个intersection:

type ToObjStepTwo<T extends Record<keyof T, PropertyAttributes>> =
  ToObjStepOne<T> extends infer T1 ?
  { [K in keyof T1]: (x: T1[K]) => void }[keyof T1] extends
  (x: infer I) => void ? I : never
  : never

type S2 = ToObjStepTwo<{⋯}>
/* type S2 = 
  Readonly<Record<"readonlyRequired", "someValue">> & 
  Partial<Readonly<Record<"readonlyOptional", "someValue">>> & 
  Record<"mutableRequired", "someValue"> & 
  Partial<Record<"mutableOptional", "someValue">> 
*/

你可以看到,这个十字路口基本上就是你想要的类型,尽管它很难看.至少这些属性的顺序是正确的.因此,如果我们现在在对象上进行最后一点映射,它将把丑陋的交叉点变成单一的对象类型:

type ToObjStepThree<T extends Record<keyof T, PropertyAttributes>> =
  ToObjStepTwo<T> extends infer T2 ?
  { [K in keyof T2]: T2[K] }
  : never

type S3 = ToObjStepThree<{⋯}>
/* type S3 = {
    readonly readonlyRequired: "someValue";
    readonly readonlyOptional?: "someValue" | undefined;
    mutableRequired: "someValue";
    mutableOptional?: "someValue" | undefined;
} */

让我们确保这是我们在运行单件定义时得到的结果:

type T1 = ToObj<{
  readonlyRequired: { readonly: true; value: "someValue" };
  readonlyOptional: { readonly: true; optional: true; value: "someValue" };
  mutableRequired: { value: "someValue" };
  mutableOptional: { optional: true; value: "someValue" };
}>;
/* type T1 = {
    readonly readonlyRequired: "someValue";
    readonly readonlyOptional?: "someValue" | undefined;
    mutableRequired: "someValue";
    mutableOptional?: "someValue" | undefined;
} */

看上go 不错!

Playground link to code

Typescript相关问答推荐

类型安全JSON解析

创建一个根据布尔参数返回类型的异步函数

不推荐使用Marker类-Google map API警告

来自类型脚本中可选对象的关联联合类型

适当限制泛型函数中的记录

错误TS2403:后续变量声明必须具有相同的类型.变量';CRYPTO';的类型必须是';CRYPATO';,但这里的类型是';CRYPATO';

具有自引用属性的接口

正交摄影机正在将渲染的场景切成两半

如何在React组件中指定forwardRef是可选的?

在Cypress中,是否可以在不标识错误的情况下对元素调用.Click()方法?

有没有可能创建一种类型,强制在TypeScrip中返回`tyPeof`语句?

S,为什么我的T扩展未定义的条件在属性的上下文中不起作用?

如何为特定参数定义子路由?

为什么TypeScrip假定{...省略类型,缺少类型,...选取类型,添加&>}为省略类型,缺少类型?

类型脚本中函数重载中的类型推断问题

从泛型对象数组构造映射类型

递归类型名称

如何判断路由或其嵌套路由是否处于活动状态?

当并非所有歧视值都已知时,如何缩小受歧视联盟的范围?

对象可能未定义(通用)