Используйте дженерики, чтобы определить возвращаемое значение для функции с размеченным объединением в качестве входных данных

Я хочу создать функцию, которая получает объект со свойствами updatedAt и/или createdAt (в виде даты) и возвращает тот же объект, но с одним или обоими значениями, сериализованными в виде строки.

Прежде всего, как определить тип возвращаемого значения этой функции?

Во-вторых, у меня есть ощущение, что это лучше сделать с помощью дженериков, но я не нашел правильного способа сделать это таким образом.

Это то, что у меня есть на данный момент:

type HasBothValues = {
  [key: string]: any;
  createdAt: Date;
  updatedAt: Date;
};

type HasCreatedAt = {
  [key: string]: any;
  createdAt: Date;
};

type HasUpdatedAt = {
  [key: string]: any;
  updatedAt: Date;
};

type AllInputVariants = HasBothValues | HasCreatedAt | HasUpdatedAt;

const serializeDates = (obj: AllInputVariants): any => { //  <-- Don't like the any return type!
  const retObj = { ...obj };
  if (obj.createdAt) {
    retObj.createdAt = obj.createdAt.toISOString();
  }

  if (obj.updatedAt) {
    retObj.updatedAt = obj.updatedAt.toISOString();
  }

  return obj;
};

export { serializeDates };

Я ценю любые советы!

Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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 для повышения производительности приложения путем загрузки модулей только тогда, когда они...
2
0
20
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Перегрузка функций кажется здесь хорошим решением.

Сначала мы создаем тип с именем Overwrite, чтобы изменить типы некоторых свойств.

type Overwrite<T, U> = Pick<T, Exclude<keyof T, keyof U>> & U extends infer O ? {
  [K in keyof O]: O[K]
} : never

Теперь мы можем добавить все возможные перегрузки функций:

function serializeDates<T extends HasBothValues>(obj: T): Overwrite<T, {updatedAt: string, createdAt: string}>
function serializeDates<T extends HasUpdatedAt>(obj: T): Overwrite<T, {updatedAt: string}>
function serializeDates<T extends HasCreatedAt>(obj: T): Overwrite<T, {createdAt: string}>
function serializeDates<T extends AllInputVariants>(obj: T) { 
  const retObj: any = { ...obj };
  if (obj.createdAt) {
    retObj.createdAt = obj.createdAt.toISOString();
  }

  if (obj.updatedAt) {
    retObj.updatedAt = obj.updatedAt.toISOString();
  }

  return retObj;
};

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


Посмотрим, работает ли это:

const t1 = serializeDates({
  createdAt: new Date(),
})
// const t1: {
//     createdAt: string;
// }

const t2 = serializeDates({
  updatedAt: new Date()
})
// const t2: {
//     updatedAt: string;
// }

const t3 = serializeDates({
  createdAt: new Date(),
  updatedAt: new Date()
})
// const t3: {
//     updatedAt: string;
//     createdAt: string;
// }

const t4 = serializeDates({
  createdAt: new Date(),
  updatedAt: new Date(),
  a: 123,
  b: "123"
})
// const t4: {
//     a: number;
//     b: string;
//     updatedAt: string;
//     createdAt: string;
// }

Если подумать, мы можем сделать лучше. Давайте изменим Overwrite, чтобы переопределять только те свойства T, которые действительно существуют в T.

type Overwrite<T, U> = (Pick<T, Exclude<keyof T, keyof U>> & Pick<U, Extract<keyof U, keyof T>>) extends infer O ? {
  [K in keyof O]: O[K]
} : never

Теперь нам больше не нужна перегрузка функций:

function serializeDates<
  T extends { updatedAt?: Date, createdAt?: Date }
>(obj: T): Overwrite<T, {updatedAt: string, createdAt: string}> { 
  const retObj: any = { ...obj };
  if (obj.createdAt) {
    retObj.createdAt = obj.createdAt.toISOString();
  }

  if (obj.updatedAt) {
    retObj.updatedAt = obj.updatedAt.toISOString();
  }

  return retObj as any;
};

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

Ничего себе, мне нужно время, чтобы изучить это. Спасибо!

Sandy 16.05.2022 13:30

@ Сэнди, я обновил свой ответ для лучшего решения

Tobias S. 16.05.2022 13:39

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