typescript - 联合类型 : PartialRequired and PickRequired

标签 typescript union-types

今天我在项目中创建了两个新的 UnionType。

您可以在代码审查中看到此类型:https://codereview.stackexchange.com/questions/223433/uniontypes-partialrequired-and-pickrequired

export type PartialRequired<T, K extends keyof T> = Partial<T> & Pick<Required<T>, K>;
export type PickRequired<T, K extends keyof T> = T & Pick<Required<T>, K>;
export type ForceId<T extends { id?: ID }, ID = number> = PickRequired<T, 'id'>;

现在我想使 id 属性可重命名?!

我尝试过这样的事情:

export type ForceId<T extends { [key: ID_NAME]?: ID }, ID = number, ID_NAME extends string = 'id'> = PickRequired<T, ID_NAME>;
//                                            ~~ ';' expected

但是正如您所看到的,这不起作用。有什么方法可以实现这样的目标吗? 所以我可以使用 ForceId<MyModel, string, 'uuid'> => { uuid: '123e4567-e89b-12d3-a456-426655440000' } 而无需创建像 ForceUuid 这样的新定义?

TypeScript Playground

编辑:

目标如下:

我有一些模型看起来像这样:

interface MyModel1 {
  id?: number; // The identifier of Model 1
  name: string;
  age?: number;
  alive?: boolean;
}

interface MyModel2 {
  uuid?: string; // The identifier of Model 2
  name: string;
  age?: number;
  alive?: boolean;
}

我不想在运行时更改任何代码。

现在我想使用 ForceId 类型。

// Works
const model1: ForceId<MyModel1> = {
  id: 0,
  name: "test",
  age: 10
};

// Don't work
const model2: ForceId<MyModel2, string, "uuid"> = {
  uuid: "123e4567-e89b-12d3-a456-426655440000",
  name: "test",
  age: 10
};

最佳答案

我将回答有关如何以编程方式修改对象类型以重命名键的一般问题。

这是一种可能的实现,它仅适用于具有已知文字键的必需的可变属性(没有可选属性,没有 readonly 属性,也没有索引签名)。如果您需要支持这些情况,这是可能的,但会变得更加难看,所以我现在忽略它。

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

type RenameKeys<T, M extends Record<keyof any, keyof any>> = ValueOf<
  { [K in keyof T]: (x: Record<K extends keyof M ? M[K] : K, T[K]>) => void }
> extends ((x: infer R) => void)
  ? { [K in keyof R]: R[K] }
  : never;

这可能会令人困惑,但基本上它需要一个对象类型 T和键映射类型 M ,并生成一个新类型,其中键已重命名,但属性类型相同。例如:

interface MyModel {
  id: number;
  name: string;
  age: number;
  alive: boolean;
}

type Renamed = RenameKeys<MyModel, { id: "uuid" }>;

产生

type Renamed = {
    uuid: number;
    name: string;
    age: number;
    alive: boolean;
}

type RenamedMulti = RenameKeys<
  MyModel,
  { id: "uuid"; alive: "notDead"; age: "yearsOld" }
>;

产生

type RenamedMulti = {
    uuid: number;
    name: string;
    yearsOld: number;
    notDead: boolean;
}

您也许可以使用RenameKeys建立您正在寻找的类型。


至于它是如何工作的:

type RenameKeys<T, M extends Record<keyof any, keyof any>> = ValueOf<
  { [K in keyof T]: (x: Record<K extends keyof M ? M[K] : K, T[K]>) => void }
> extends ((x: infer R) => void)
  ? { [K in keyof R]: R[K] }
  : never;

M映射扩展Record<keyof any, keyof any>只是确保它是从键到键的映射。然后,让我们想象一下:{[K in keyof T]: Record<K extends keyof M ? M[K] : K, T[K]>]} 。这基本上占据了 T 中的所有属性并查找 M查看该键是否已映射,如果是,则映射它...如果您这样做了,则 TMyModelM{id: "uuid"} ,那么你会得到 {id: {uuid: number}, name: {name: string}, age: {age: number}, alive: {alive: boolean}}

从这种类型转换为具有这些组合的类型有点棘手...我的方法是使用 conditional type inference将这些类型的并集转换为交集。首先,我将这些属性放入函数参数中,例如 (x: Record<...> => void) 。然后我得到函数的并集(即 ValueOf<> 应用程序)并推断出参数类型为 R 的单个函数。从中。这最终成为一个交叉点,因为 (x: A)=>void | (x: B)=>void可分配给(x: A & B) => void 。 (其工作原理的另一个解释是 here 或可能 here )

所以类型 R结果看起来像 {uuid: number} & {name: string} & {age: number} & {alive: boolean} ,然后 {[K in keyof R]: R[K]}是一个“身份”映射类型,它将这些属性收集到 {uuid: number; name: string; age: number; alive: boolean} 中.


好的,希望有帮助;祝你好运!

Link to code

关于typescript - 联合类型 : PartialRequired and PickRequired,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56876112/

相关文章:

scala - 带有子类型 : A|B <: A|B|C 的 Scala 中的联合类型

TypeScript:Promise.all 不处理联合类型

typescript - 如何将泛型限制为联合?

Typescript:函数中的可选参数,取决于联合类型是否具有属性

typescript - 赋值前使用变量的常见模式

javascript - 如何从 typescript 中的字符串中减去日?

TypeScript 无法对联合类型进行类型解析

javascript - 多种图案的 Angular 管

typescript - 如何自动修复 vuejs 中的 tslint 错误

javascript - 为什么 babel 将字符串插值转换为 concat?