Создайте тип, имеющий общие реквизиты из других типов и другие необязательные

У меня есть следующие три интерфейса:

type A = {
    x: string
    y: string
}

type B = {
    y: string
}

type C = {
    z: string;
    y: string;
}

Здесь есть общие обязательные свойства. И есть обязательные свойства, которые не являются частью остальных двух типов. Итак, мой вопрос:

Есть ли способ создать тип, который объединяет общие реквизиты и делает другие свойства необязательными? Например.:

type D = {
    y: string
    x?: string
    z?: string
}

Любые идеи?

Во многих случаях тип объединения A | B | C будет вести себя таким образом, но более строго в том смысле, что вы не можете иметь «необязательные» свойства более чем из одного из них.

kaya3 16.03.2022 18:01
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
1
1
47
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Работая над этим решением, я попытался создать реализацию, в которой использовались бы дженерики, но ничего не вышло. Я уверен, что мастер TypeScript мог бы получить общее решение из моего!

Во-первых, давайте создадим служебный тип для поиска «общих» свойств:

type Common = {
  [key in keyof A & keyof B & keyof C]: A[key] | B[key] | C[key]
}

Теперь мы можем найти свойства одного из ваших интерфейсов, которые являются «необычными»:

{
  // Properties only in A
  [key in keyof Exclude<A, keyof Common>]?: A[key]
}

Если сложить все вместе, то получим следующее:

export interface A {
  x: string
  y: string
}

export interface B {
  y: string
}

export interface C {
  z: string
  y: string
}

/**
 * Common properties in A, B, and C.
 */
type Common = {
  [key in keyof A & keyof B & keyof C]: A[key] | B[key] | C[key]
}

/**
 * All properties from A, B, and C.
 * 
 * Properties that are "common" (in all interfaces) are required,
 * others (not present in all interfaces) are optional.
 */
type D = Common & {
  // Properties only in A
  [key in keyof Exclude<A, keyof Common>]?: A[key]
} & {
  // Properties only in B
  [key in keyof Exclude<B, keyof Common>]?: B[key]
} & {
  // Properties only in C
  [key in keyof Exclude<C, keyof Common>]?: C[key]
}

ТС игровая площадка

Ответ принят как подходящий

Есть довольно много способов сделать это, но одним из решений было бы создать тип Pick<T1, keyof T1 & keyof T2 & keyof T3>, который имеет свойства, общие для трех типов от T1 до T3, и пересечь его с объектом Partial<T1 & T2 & T3>, у которого есть необязательные свойства для любых свойств в T1 до T3.

type CommonRequiredRestOptional<T1, T2, T3> =
  Pick<T1, keyof T1 & keyof T2 & keyof T3> & Partial<T1 & T2 & T3>

Частичный объект также будет содержать требуемые свойства, но из-за пересечения они остаются обязательными (T & (T | undefined) есть T).

Помощник TopLevelNormalize можно использовать для отображения фактического типа объекта, а не Pick<A, "y"> & Partial<A & B & C>:

type TopLevelNormalize<T> = T extends infer S ? {[K in keyof S]: S[K]} : never;

type Test = TopLevelNormalize<CommonRequiredRestOptional<A, B, C>>
// type Test = { y: string; x?: string | undefined; z?: string | undefined;}

Площадка TypeScript

Я хотел поделиться более общим ответом на случай, если кто-то найдет его полезным.

Довольно обширно, но в конце концов вам нужно будет использовать только тип CommonUncommon (из-за отсутствия лучшего имени)

export interface A {
  x: string;
  y: string;
}

export interface B {
  y: string;
}

export interface C {
  z: string;
  y: string;
}

export interface D {
  y: string;
  justAnotherProp: unknown[];
}

type IsInAll<T extends unknown[], Prop extends number | string | Symbol> = T extends [infer Head, ...infer Tail]
  ? Prop extends keyof Head
    ? IsInAll<Tail, Prop>
    : false
  : true;
type ExcludeArr<T extends unknown[], X, Res extends unknown[] = []> = T extends [infer Head, ...infer Tail]
  ? Head extends X
    ? X extends Head
      ? ExcludeArr<Tail, X, Res>
      : ExcludeArr<Tail, X, [...Res, Head]>
    : ExcludeArr<Tail, X, [...Res, Head]>
  : Res;
type Normalize<T> = {[K in keyof T]: T[K]};
type Unite<T extends unknown[], K extends unknown[] = T, Res = unknown> = T extends [infer Head, ...infer Tail] 
  ? Unite<Tail, K, {
    [Prop in keyof Head as true extends IsInAll<ExcludeArr<K, Head>, Prop> ? Prop : never]: Head[Prop]; 
  } 
  & 
  {
    [Prop in keyof Head as false extends IsInAll<ExcludeArr<K, Head>, Prop> ? Prop : never]?: Head[Prop]; 
  }
  &
  Res>
  : Res;

type CommonUncommon<T extends unknown[]> = Normalize<Unite<T>>;

type res1 = CommonUncommon<[A, B]>;
type res2 = CommonUncommon<[A, B, C]>;
type res3 = CommonUncommon<[A, B, C, D]>;

Детская площадка

Другие вопросы по теме