Я пишу функцию высшего порядка, opposite(fn)
. Этот opposite
принимает логическую или числовую fn
функцию в качестве входных данных и возвращает новую функцию, которая должна быть того же типа, что и fn
. Если исходная функция имеет тип, возвращающий логическое значение, новая функция возвращает «не то логическое значение». Если исходная функция имеет тип, возвращающий число, новая функция возвращает 1 или 0 в зависимости от того, был ли результат нулевым или нет. Вы можете использовать opposite
, чтобы инвертировать фильтр с someArray.filter(testCondition)
на someArray.filter(opposite(testCondition))
.
Проблема: оба return
предложения выдают ошибку Type 'number' is not assignable to type 'ReturnType<T>' ts(2322)
.
Почему я не могу присвоить boolean
или number
boolean|number
? Как мне применять типы данных к моей функции opposite
?
const opposite =
< T extends
| ((...args: any[]) => boolean)
| ((...args: any[]) => number)
>(fn: T) =>
(...args: Parameters<T>): ReturnType<T> => {
const result = fn(...args);
if (typeof result === "number") {
return result === 0 ? 1 : 0;
} else {
return !result;
}
};
Я спотыкаюсь о такие вещи каждый раз, когда снова беру в руки ТС.
Я не думаю, что вы можете ввести это без частичного отключения проверки типов тем или иным образом.
Обратите внимание, что я добавил утилиту Widen
, потому что ваша подпись функции была неправильной: если fn
— это () => true as const
, то opposite(fn)
— это () => true
вместо () => boolean
type Widen<T> = T extends number ? number : T extends boolean ? boolean : T;
const opposite =
< T extends
| ((...args: any[]) => boolean)
| ((...args: any[]) => number)
>(fn: T) =>
(...args: Parameters<T>): Widen<ReturnType<T>> => {
const result = fn(...args);
if (typeof result === "number") {
return result === 0 ? 1 : 0 as any;
// ------
} else {
return !result as any;
// ------
}
};
const opposite:{
<Args extends unknown[]>(f: (...args: Args) => boolean): (...args: Args) => boolean
<Args extends unknown[]>(f: (...args: Args) => number): (...args: Args) => 0 | 1
} = <Args extends unknown[]>(f: (...args: Args) => boolean | number) => (...args: Args): any => {
// ---
const result = f(...args);
if(typeof result === 'number') {
return result === 0 ? 1 : 0;
} else {
return !result;
}
};
type Widen<T> = T extends number ? number : T extends boolean ? boolean : T;
const opposite =
<Args extends unknown[], R extends boolean | number>
(f: (...args: Args) => R) => (...args: Args): Widen<R> => {
const result = f(...args);
if(typeof result === 'number') {
return result === 0 ? 1 : 0 as any;
// ------
} else {
return !result as any;
// ------
}
};
Несколько вещей могут привести к тому, что он не скомпилируется:
ReturnType<T>
не определено: это не number | boolean
, и компилятор не вычислит значение.boolean & number
— это never
.R
может быть предоставлен вызывающей стороной: если они передают 5
, boolean
больше не расширяется R
.Я могу немного ошибаться в этих интерпретациях, но эта ментальная модель работает довольно хорошо, и суть в том, что вы застряли.
Будьте осторожны, использование any
не позволяет TS делать какие-либо выводы: any
расширяет любой тип, а любой тип расширяет any
, поэтому его использование фактически отключает проверку типов. В этом случае тип возвращаемого значения и ограничения типа полезны для вызывающих, но реализация не проверяет тип, поэтому как разработчик вы должны быть осторожны, чтобы выполнить свой контракт.
что касается Widen
, взгляните на эту игровую площадку: typescriptlang.org/play?#code/… Вы не можете вернуть ReturnType<T>
, потому что тип возврата может быть слишком узким
О том, как пишется Widen
: T extends number ? number : ...
означает «если T
есть 5
, вернуть number
вместо 5
». opposite(() => 5)
не должен быть типа () => 5
, а () => number
или () => 0
, если хотите быть более точным
Основываясь на ответе Джеффри, я сократил и исправил свой код. Мне не очень нравилась конструкция
Widen<T>
; по сути, он говорит: «Если T возвращает число, то число; иначе, если T возвращает логическое значение, логическое значение; в противном случае, что бы ни возвращал T» - для меня это было похоже на то, чтобы сказать «все, что возвращает T». Мы можем обойтись безWiden
; настоящим ключом были изменения, которые выделил Джеффри, переход отreturn ... as number
кreturn ... as any
. Это интересно; вместо того, чтобы сообщать TypeScript тип ответа, мы должны позволить ему понять это самому!