UnionTypes: PartialRequired и PickRequired - PullRequest
1 голос
/ 03 июля 2019

Сегодня я создал два новых UnionTypes в моем проекте.

Вы можете увидеть эти типы здесь в Code-Review: 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

Edit:

Цель состоит в следующем:

У меня есть несколько моделей, которые выглядят так:

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
};

1 Ответ

2 голосов
/ 03 июля 2019

Я собираюсь ответить на общий вопрос о том, как программно изменить тип объекта для переименования ключей.

Вот одна из возможных реализаций, которая действительно работает только для требуемых, изменяемых свойств с известными литеральными ключами (не обязательносвойства, без 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, чтобы увидеть, сопоставлен ли ключ, и если да, отображает его ... Если вы сделали это, где T было MyModel и M было{id: "uuid"}, тогда вы получите {id: {uuid: number}, name: {name: string}, age: {age: number}, alive: {alive: boolean}}.

Немного сложно перейти от этого к типу с этими объединенными ... я делаю это с выводом условного типа , чтобы превратить объединение этих типов в пересечение.Сначала я помещаю эти свойства в параметры функции, например (x: Record<...> => void).Затем я получаю объединение функций (это приложение ValueOf<>) и выводю из него одну функцию с типом параметра R.Это заканчивается пересечением, поскольку (x: A)=>void | (x: B)=>void присваивается (x: A & B) => void.(Другое объяснение того, как это работает, здесь или, возможно, здесь )

Таким образом, тип R, который получается, выглядит как {uuid: number} & {name: string} & {age: number} & {alive: boolean}, а затем {[K in keyof R]: R[K]} является сопоставленным типом «identity», который объединяет эти свойства в {uuid: number; name: string; age: number; alive: boolean}.


Хорошо, надеюсь, это поможет;удачи!

Ссылка на код

...