Общий объект Typescript в массиве соответствует только указанным записям

Демо: https://tsplay.dev/Ннавав

Итак, у меня есть массив со следующим определением:

Array<{
      id?: string;
      text?: string;
      date?: Date;
    }>

Это мешает следующей реализации:

data: Array<Partial<Record<K, string>> & Partial<Record<H, string | number | null>>>

Как я могу сообщить Typescript, что массив также может включать другие свойства, кроме Partial<Record<K, string>> & Partial<Record<H, string | number | null>>?

Потому что, если я передам массив со следующим определением, это даст мне эту ошибку:

Type 'Date' is not assignable to type 'string | number | null | undefined'.

Полная функция:

ifAlreadyExistsString<K extends PropertyKey, H extends PropertyKey>(
    data: Array<Partial<Record<K, string>> & Partial<Record<H, string | number | null>>>,
    key: K,
    value: string,
    idKey?: H,
    idValue?: string | number | null
  ): boolean {
    return (
      data.filter((item) => {
        // If the value is found in the data array
        if (item[key] && item[key]?.trim().toLowerCase() === value.trim().toLowerCase()) {
          // Then check if the id of the value matches the found entry
          // If the ids are matching, then you are currently editing this exact entry
          // If the ids are NOT matching, then you have found a duplicate.
          if (idKey && item[idKey] && idValue) {
            return !(item[idKey] === idValue);
          } else {
            // If no idKey is provided, then we have found a duplicate.
            return true;
          }
        }

        return false;
      }).length !== 0
    );
  }
data: Array<Partial<Record<K, string>> & Partial<Record<H, string | number | Date | null>>> исправляет ошибку. Это то, что вы хотели?
Mike S. 13.05.2022 10:27

Нет, потому что вы изменяете тип данных idKey/H. Структура объекта может быть любой, но она должна включать определения записей.

dewey 13.05.2022 11:07

Вывод - это неспособность сделать то, что вы хотите, когда idKey не передано. Я вижу два варианта; используйте перегрузки, такие как это, или используйте трюк NoInfer, чтобы попытаться предотвратить использование data для вывода H, например это. Дайте мне знать, какой из них соответствует вашим потребностям, и я напишу ответ с объяснением. Если ни один из них не соответствует вашим потребностям, что мне не хватает?

jcalz 14.05.2022 00:03

@jcalz, использующий трюк NoInfer, отлично работает, спасибо!

dewey 16.05.2022 09:22

@jcalz Есть ли способ заставить автозаполнение работать для key и idKey? Чтобы вы могли вводить только обязательные или необязательные свойства модели в массиве? В настоящее время я могу передать erkusdsdsd как ключ, даже если его нет в модели.

dewey 16.05.2022 09:25

Автозаполнение, кажется, выходит за рамки заданного вопроса. Это нужно для моего ответа? Если да, пожалуйста, редактировать вопрос, чтобы уточнить ваши требования, желательно с указанием вариантов использования. Если нет, то я с удовольствием посоветую в другом посте, предполагая, что вы не можете найти ответ на свой вопрос, выполнив поиск SO.

jcalz 16.05.2022 17:15

@jcalz Спасибо, я проведу дополнительные исследования и при необходимости открою еще один вопрос.

dewey 16.05.2022 17:57
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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
7
42
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Если вы не передадите параметр idKey, компилятор не сможет использовать его для вывода H. Здесь вы хотели бы, чтобы H возвращалось к never, потому что Record<never, string | number | null> — это просто {}, и вы не хотите ограничивать тип элемента data. К сожалению, на самом деле происходит из-за того, что компилятор использует тип массива data для вывода H. Он догадывается, что H должно быть keyof (typeof data)[number], а потом жалуется на это. Ну что ж.

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


Один из способов сделать это — метод перегрузка, чтобы была одна сигнатура вызова для присутствия idKey и другая — для его отсутствия. Это выглядит так:

// call signature without idKey
ifAlreadyExistsString<K extends PropertyKey>(
  data: Array<Partial<Record<K, string>>>,
  key: K,
  value: string,
): boolean;

// call signature with idKey
ifAlreadyExistsString<K extends PropertyKey, H extends PropertyKey>(
  data: Array<Partial<Record<K, string>> & Partial<Record<H, string | number | null>>>,
  key: K,
  value: string,
  idKey?: H,
  idValue?: string | number | null
): boolean;

// implementation
ifAlreadyExistsString<K extends PropertyKey, H extends PropertyKey>(
  data: Array<Partial<Record<K, string>> & Partial<Record<H, string | number | null>>>,
  key: K,
  value: string,
  idKey?: H,
  idValue?: string | number | null
): boolean { /* snip impl */ }

Вы обнаружите, что теперь это работает:

console.info(this.ifAlreadyExistsString(data, 'text', 'no text')); // okay
/* AppComponent.ifAlreadyExistsString<"text">(
    data: Partial<Record<"text", string>>[], key: "text", value: string
  ): boolean (+1 overload) */

Еще одна вещь, которую вы можете сделать, это попытаться указать компилятору не выводить H из data, а По умолчанию из never. Вы хотите сказать, что H в типе data — это использование параметра невыводимого типа. В Майкрософт/TypeScript#14829 есть запрос функции для синтаксиса для этого. Идея состоит в том, что NoInfer<H> будет точно равно H, но не будет использоваться для вывода.

Запрос функции все еще открыт. Однако есть несколько способов эмулировать NoInfer<T> с текущими функциями TypeScript, которые работают по крайней мере в некоторых обстоятельствах. В одну сторону — это:

type NoInfer<T> = [T][T extends any ? 0 : never];

Вы можете видеть, что независимо от того, что такое T, NoInfer<T> в конечном итоге будет оцениваться как T. Но компилятор откладывает оценивает T extends any ? 0 : never для универсального T, и это блокирует вывод:

ifAlreadyExistsString<K extends PropertyKey, H extends PropertyKey = never>(
  data: Array<Partial<Record<K, string>> & Partial<Record<NoInfer<H>, string | number | null>>>,
  key: K,
  value: string,
  idKey?: H,
  idValue?: string | number | null
): boolean { /* snip impl */ }

И теперь это снова работает:

console.info(this.ifAlreadyExistsString(data, 'text', 'no text'));
/* AppComponent.ifAlreadyExistsString<"text", never>(
     data: (Partial<Record<"text", string>> & Partial<Record<never, string | number | null>>)[], 
     key: "text", value: string, idKey?: undefined, 
     idValue?: string | number | null | undefined): boolean */

Ссылка на код для игровой площадки

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