Демо: 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
);
}
Нет, потому что вы изменяете тип данных idKey
/H
. Структура объекта может быть любой, но она должна включать определения записей.
Вывод - это неспособность сделать то, что вы хотите, когда idKey
не передано. Я вижу два варианта; используйте перегрузки, такие как это, или используйте трюк NoInfer
, чтобы попытаться предотвратить использование data
для вывода H
, например это. Дайте мне знать, какой из них соответствует вашим потребностям, и я напишу ответ с объяснением. Если ни один из них не соответствует вашим потребностям, что мне не хватает?
@jcalz, использующий трюк NoInfer
, отлично работает, спасибо!
@jcalz Есть ли способ заставить автозаполнение работать для key
и idKey
? Чтобы вы могли вводить только обязательные или необязательные свойства модели в массиве? В настоящее время я могу передать erkusdsdsd
как ключ, даже если его нет в модели.
Автозаполнение, кажется, выходит за рамки заданного вопроса. Это нужно для моего ответа? Если да, пожалуйста, редактировать вопрос, чтобы уточнить ваши требования, желательно с указанием вариантов использования. Если нет, то я с удовольствием посоветую в другом посте, предполагая, что вы не можете найти ответ на свой вопрос, выполнив поиск SO.
@jcalz Спасибо, я проведу дополнительные исследования и при необходимости открою еще один вопрос.
Если вы не передадите параметр 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 */
data: Array<Partial<Record<K, string>> & Partial<Record<H, string | number | Date | null>>>
исправляет ошибку. Это то, что вы хотели?