Аргумент типа 'строка | номер | boolean» нельзя присвоить параметру типа «никогда». ц2345

Я использую следующий тип объединения

interface TypeMap {
  boolean: boolean;
  number: number;
  string: string;
}

interface Item {
  id: string;
}

type FieldHeader<I extends Item> = {
  [Type in keyof TypeMap]: {
    compare: (a: TypeMap[Type], b: TypeMap[Type]) => number;
    filter: (value: TypeMap[Type], input: TypeMap[Type]) => boolean;
    get: (item: I) => TypeMap[Type] | null | undefined;
    type: Type;
  };
}[keyof TypeMap];

Параметр

type TableHeader<I extends Item> = Record<
  string,
  FieldHeader<I>
>;

type Filter<I extends Item, T extends TableHeader<I>> = {
  [Field in keyof T]: TypeMap[T[Field]['type']];      // this isn't working
}

во-первых, тип свойств Filter выводится неправильно. Учитывая определение Item и TableHeader, я мог бы определить Filter с любым ключом свойства и любым значением без каких-либо ошибок, см. здесь (л. 44)

А если я поставлю

function applyFilter<I extends Item, T extends TableHeader<I>>(item: I, tableHeader: T, filter: Filter<I, T>) {
  Object.entries(filter).forEach(([key, input]) => {
    const value = tableHeader[key].get(item);

    if (value != null) tableHeader[key].filter(value, input);
  })
}

я получаю сообщение об ошибке

Argument of type 'string | number | boolean' is not assignable to parameter of type 'never'.
  Type 'string' is not assignable to type 'never'.(2345)

(см. здесь, л. 56) что не совсем удивительно, вопрос в том, как мне устранить эту ошибку?

Это классика. Пожалуйста, смотрите мою статью . tableHeader[key].filter производит объединение функций. Обычно это не то, что вы хотите. Обычно вам нужно пересечение функций. Пожалуйста, имейте в виду, что пересечения функций - это фактически перегрузка функций. Приведите пример того, как вы хотите вызвать эту функцию. Короче говоря, вам нужно изменить свой подход

captain-yossarian from Ukraine 28.05.2023 09:28

Вам потребуется провести рефакторинг, чтобы убедить компилятор в том, что это безопасно, как описано в ms/TS#47109 . Похоже на эту версию; это полностью отвечает на ваш вопрос? Если да, то я напишу ответ; если нет, то что мне не хватает?

jcalz 28.05.2023 18:22

Спасибо вам всем. Я бы придерживался подхода @jcalz, было бы здорово, если бы вы написали ответ

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

Ответы 1

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

TypeScript не может отслеживать корреляции между типами союзов ; если у вас есть один блок кода, такой как ваше тело обратного вызова forEach(), компилятор может проанализировать его только один раз. Если у вас есть несколько выражений в таком блоке кода, которые зависят от одного и того же значения типа объединения, компилятор обычно просто обрабатывает эти выражения как сами типы объединения, но обрабатывает их независимо. Таким образом, функция, зависящая от ключа объединения, станет объединением функций. И аргумент, который зависит от ключа объединения, станет объединением аргументов. Но вы не можете безопасно вызывать объединение функций с объединением аргументов; компилятор будет настаивать на пересечении аргументов . Это тема microsoft/TypeScript#30581.

Самый простой подход здесь — просто использовать утверждения типа, чтобы скрыть ошибки, потому что вы уверены, что делаете это правильно.

Однако, если вы хотите, чтобы компилятор проверял тип того, что вы делаете, вы можете выполнить довольно большой рефакторинг, чтобы использовать общие индексы в отображаемые типы , как описано в microsoft/TypeScript#47109 .

Этот код уже прошел один раунд такого рефакторинга, но теперь вам нужно сделать это для типа отображения T из ключей заголовка таблицы в конкретный тип поля. Мы должны сохранить FieldHeader общее в K extends keyof TypeMap, чтобы сделать это; это нормально, если FieldHeader<I> по умолчанию является объединением, но мы хотим иметь возможность выбирать конкретное K:

type FieldHeader<I extends Item, K extends keyof TypeMap = keyof TypeMap> = {
  [P in K]: {
    compare: (a: TypeMap[P], b: TypeMap[P]) => number;
    filter: (value: TypeMap[P], input: TypeMap[P]) => boolean;
    get: (item: I) => TypeMap[P] | null | undefined;
    type: P;
  };
}[K];

И теперь ваши типы должны быть универсальными для этого отображения T:

type TableHeader<I extends Item, T extends Record<keyof T, keyof TypeMap>> = {
  [K in keyof T]: FieldHeader<I, T[K]>
}

type Filter<I extends Item, T extends Record<keyof T, keyof TypeMap>> = {
  [K in keyof T]: TypeMap[T[K]];
}

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

const tableHeader: TableHeader<MyItem, { selected: "boolean" }> = {
  selected: {
    compare: (a, b) => +a - +b,
    filter: (value, input) => value === input,
    get: (video) => video.selected,
    type: 'boolean',
  },
};

const filter: Filter<MyItem, { selected: "boolean" }> = {
  // ts should throw an error here, as type of "selected" should be 'boolean'
  selected: "",

  // also only props of "tableHeader" should be allowed
  foo: 123
}

И, наконец, ваша функция applyFilter() также является универсальной в T, например:

function applyFilter<I extends Item, T extends Record<keyof T, keyof TypeMap>>(
  item: I, tableHeader: TableHeader<I, T>, filter: Filter<I, T>
) {
  (Object.keys(filter) as Array<keyof T>).forEach(<K extends keyof T>(key: K) => {
    const input = filter[key];
    const value = tableHeader[key].get(item);
    if (value != null) tableHeader[key].filter(value, input);
  });
}

Обратите внимание, что нам все еще нужно утверждение типа, чтобы компилятор увидел, что записи filter имеют какое-либо отношение к keyof T, и на самом деле мы не можем использовать Object.entries(), потому что это не позволит нам представить связь между K и Filter<I, T>[K]. Но в остальном это работает; компилятор может видеть, что input относится к типу TypeMap[T[K]], и что value относится к типу TypeMap[T[K]] (после проверки на нулевое значение), и что tableHeader[key].filter относится к типу (value: TypeMap[T[K]], input: TypeMap[T[K]]) => boolean, так что все типы проверяются.

Площадка ссылка на код

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