TypeScript: Type '{id: строка; } & Pick ‹T, Exclude‹ keyof T, id ›› 'не назначается типу' T '

У меня есть такая функция:

const createRelationship = <T extends { id: string }>(
  includedItems: Array<DataResponse<T>>,
  categories: Array<{ id: string, type: string }>,
): Array<T> => {
  return categories.map(({ id }) =>
    includedItems.find((includedItem) => includedItem.id === id) as DataResponse<T>)
  .map(flatAttributes);
};

Он принимает общий тип T. Первый аргумент - это массив DataResponse, где:

interface DataResponse<T extends { id: string }, R = void> {
  id: string;
  attributes: Omit<T, 'id'>;
}

и:

type Omit<A extends object, K extends keyof A> = Pick<A, Exclude<keyof A, K>>;

so

type Example = DataResponse<{ id: string, key: number }> // { id: string, attributes: { key: number } };

Функция flatAttributes выглядит так:

const flatAttributes = <T extends { id: string, attributes: any }>(item: T): { id: string } & T['attributes'] =>
  ({...item.attributes, ...item });

Это своего рода обратная операция, например:

flatAttributes{ id: string, attributes: { key: number } }) === { id: string, key: number }

Теперь к делу: Сборник createRelationship торв:

Type '({ id: string; } & Pick<T, Exclude<keyof T, "id">>)[]' is not assignable to type 'T[]'.
  Type '{ id: string; } & Pick<T, Exclude<keyof T, "id">>' is not assignable to type 'T'.

Где Pick<T, Exclude<keyof T, "id">>' - это Omit<T, 'key'>, а Omit<T, 'key'> & {key: string} должно быть одинаковым T.

Есть идеи, как объявить эту функцию без небезопасного сопоставления? И почему TypeScript так себя ведет?

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


person Przemyslaw Jan Beigert    schedule 14.03.2019    source источник
comment
Я бы рекомендовал вам просто использовать утверждение типа, если компилятор жалуется, но вы считаете, что то, что вы делаете, безопасно. Вот для чего нужны утверждения типа. (Обратите внимание, что вам следует обратить особое внимание перед использованием утверждения, чтобы убедиться, что оно действительно безопасно. Например, если T равно {id: "stringLiteralValue"}, тогда T можно назначить {id: string} , но не наоборот < / i>. Только вы знаете, заботитесь ли вы об этих крайних случаях.)   -  person jcalz    schedule 14.03.2019
comment
Да, после добавления as Array<T> после возврата ошибки компиляции исчезнут. Но почему TS считает, что Omit<T, 'key'> & {key: string} не совсем то же самое, что T. Особенно id атрибут должен существовать в типе T   -  person Przemyslaw Jan Beigert    schedule 14.03.2019
comment
Знаете ли вы о строковых литералах? Как я уже сказал, когда T равно {id: "stringLiteralValue"}, T может быть назначен {id: string}, но {id: string} не может быть назначен T. Они не одного типа.   -  person jcalz    schedule 14.03.2019
comment
Даже когда два типа в точности равны, компилятор все еще может не распознать это, потому что либо: анализ, необходимый для его определения, требует уровня интеллекта человека, который, к сожалению, все еще отсутствует в компиляторе TS начиная с версии v3. 4; или анализ прост, но делать это все время будет слишком дорого и слишком сильно замедлит компилятор, чтобы того стоить.   -  person jcalz    schedule 14.03.2019
comment
Да, я их знаю и понимаю, почему мне разрешили делать as Array<T> (спасибо!). Но мне все еще интересно, почему TS не рассчитал новый тип. Тем более что Omit ‹T, 'id'› нужно было вычислить, а оператор & вычислить довольно просто.   -  person Przemyslaw Jan Beigert    schedule 14.03.2019