Типизируйте обёртку memoize, сохраняющую аргументы и результат функции
memoize принимает чистую функцию и возвращает её кэширующую версию.
Требования:
результата — и то и другое проверяется в каждом месте вызова, без any в видимой сигнатуре;
- возвращаемая функция должна принимать параметры обёрнутой функции и возвращать её тип
- повторный вызов с теми же аргументами не должен снова запускать обёрнутую функцию;
- получать ключ кэша сериализацией аргументов допустимо.
export function memoize(fn) {
// ваш код здесь
}
Допишите реализацию.
Обобщайтесь по типу функции и пересоберите из него сигнатуру: (...args: Parameters<T>) => ReturnType<T>. Parameters<T> достаёт кортеж аргументов, ReturnType<T> — результат, поэтому кэширующая версия проверяется ровно как исходная.
- ✗Возвращать
(...args: any[]) => any, теряя все проверки, что были у исходной функции - ✗Типизировать обёрнутую функцию как
Function— тогдаParameters<T>к ней не применить - ✗Забывать, что ключ кэша строится из аргументов, а не из
T
- →Как
ReturnType<T>достаёт результат черезinfer? - →Что сломается, если обёрнутая функция перегружена?
Обёртка обобщена по типу функции, а её сигнатура пересобрана из Parameters<T> и ReturnType<T> — это и есть роль этих утилитных типов.
export function memoize<T extends (...args: any[]) => unknown>(
fn: T,
): (...args: Parameters<T>) => ReturnType<T> {
const cache = new Map<string, ReturnType<T>>();
return (...args: Parameters<T>): ReturnType<T> => {
const key = JSON.stringify(args);
if (!cache.has(key)) {
cache.set(key, fn(...args) as ReturnType<T>);
}
return cache.get(key) as ReturnType<T>;
};
}
const area = memoize((w: number, h: number): number => w * h);
area(2, 3); // number
area('2', 3); // Ошибка: 'string' не присваивается 'number'
Parameters<T> даёт кортеж аргументов, ReturnType<T> — тип результата. Обе — условные типы с infer поверх сигнатуры T, поэтому у вызывающего сохраняются и арность, и типы аргументов, и тип результата. Верните вместо этого (...args: any[]) => any — и area('2', 3) пройдёт молча.