#typescript #interface #typeerror #covariance #contravariance
#typescript #интерфейс #ошибка типа #ковариация #контравариантность
Вопрос:
У меня есть следующий тип, который устанавливает, что все свойства будут функциями, которые не принимают либо никаких аргументов, либо один аргумент типа Record<string, any>
:
type FnTrait = Record<
string,
(input?: Record<string, any>) => any
>;
Я пытаюсь расширить этот тип на другой интерфейс (который я хочу иметь с тем же ограничением).
interface HasFn extends FnTrait {
someFn(): string; // no problem
anotherFn(o: {id: string}): number; // ts error 2411
}
Это приводит к ошибке на anotherFn
: Property 'anotherFn' of type '(o: { id: string; }) => number' is not assignable to string index type '(input?: Record<string | number | symbol, any> | undefined) => any'.
Почему это someFn
не приводит к ошибке, в то время как anotherFn
выдает ошибку ts 2411? Кажется, что это сужение должно быть разрешено.
Будем признательны за любую помощь. Спасибо!
Ответ №1:
Это пример того, что типы функций контравариантны по своим параметрам. Слово «контравариантный» означает «изменяется противоположным образом». Если вы делаете параметр функции более специфичным (узким), вы делаете сам тип функции более общим (широким). Это означает, что вместо того, чтобы создавать HasFn
подтип FnTrait
(что extends
и означает), вы как бы делаете его супертипом. Это недопустимо и нарушает принцип взаимозаменяемости.
В частности, anotherFn()
ошибка, потому что она требует, чтобы ее аргумент имел свойство с string
значением id
, в то время как FnTrait["anotherFn"]
этого не делает. Ожидается, что вы можете вызвать любое свойство FnTrait
либо без параметров, либо с одним параметром практически любого типа. Но HasFn
может произойти сбой, если вы вызовете его anotherFn()
метод без параметра или с параметром, в котором отсутствует правильное id
свойство. Поэтому, как определено, HasFn
не может быть присвоено FnTrait
, несмотря на то, что оно объявлено для его расширения:
const hasFn: HasFn = {
someFn: () => "",
anotherFn: o => o.id.length
}
const fnTrait: FnTrait = hasFn;
fnTrait.anotherFn({ a: 123 }); // okay at compile time, explodes at runtime
Поскольку anotherFn()
означает, что вы не можете безопасно подставить FnTrait
значение там, где запрашивается HasFn
значение, FnTrait
не может быть присвоено HasFn
, и вы получаете ошибку.
Причина, по которой someFn()
ошибка не заключается в том, что функция с меньшим количеством параметров может быть назначена функции, которая принимает больше параметров. Это связано с тем, что someFn()
по необходимости будет игнорировать любые переданные в него параметры, поэтому безопасно рассматривать его как функцию, которая, возможно, может получить параметр:
fnTrait.someFn({ a: 123 }); // okay at compile time and runtime
Это работает по той же причине, anotherFn()
по которой происходит сбой: взаимозаменяемость.
Комментарии:
1. спасибо вам за ваше потрясающее описание! Еще один вопрос: каков самый минимальный способ позволить пользователям накладывать ограничение типа аргумента метода?…
extends FnTrait
чист. Какие-либо другие способы, которые вы можете порекомендовать, которые не приводят к ошибке контравариантности?