Как типизировать функцию мемоизации

  —  3 минуты

#typescript#code
Читать статью в Telegram

В одной из прошлых статей разбирал реализацию функции мемоизации на JavaScript и упомянул, что типов там нет осознанно. В этой статье мы отдельно разберём типизацию такой функции, это будет полный разбор для самых маленьких.

Шпаргалка по используемым в посте utility-типам:

typescript
1Parameters<T> // возвращает тип аргументов функции T
2ReturnType<T> // возвращает тип возвращаемых значений функции T
3
4const foo = (a: number , b: number) => 5
5
6type Args = Parameters<typeof foo> 
7// [a: number, b: number]
8
9type Return = ReturnType<typeof foo>
10// number
11

Итак, наша функция принимает в себя другую функцию. Сразу же типизируем это:

typescript
1function memoize(func: (...args: any[]) => any) {
2    // ...
3}
4

Далее было бы неплохо типизировать возвращаемую функцию.

Для этого мы можем использовать следующую конструкцию:

typescript
1(...args: Parameters<оригинальная функция>) => ReturnType<оригинальная функция>
2

И вот мы сталкиваемся с проблемой, что для корректной типизации возвращаемой функции нам необходимы типы Parameters и ReturnType, которые обязательно принимают дженерик. В качестве дженерика выступает тип оригинальной функции. Для удобства вынесем тип оригинальной функции в дженерик функции memoize:

typescript
1function memoize<T extends (...args: any[]) => any>(func: T) {
2    // ...
3}
4

И итоговый интерфейс функции у нас сильно растянется, но будет таким:

typescript
1type AnyFunction = (...args: any[]) => any;
2type MemoizedFunction<T extends AnyFunction> = 
3  (...args: Parameters<T>) => ReturnType<T>
4
5function memoize<T extends AnyFunction>(func: T): MemoizedFunction {
6    // ...
7}
8

Ребята, стараюсь максимально упростить. Если плохо понимаете дженерики, постарайтесь более внимательно вчитываться в код.

Далее типизация кэша, тут всё просто:

typescript
1const cache = new Map<string, ReturnType<T>>();
2

По типизации, ключ — строка, а значение — тот тип, который возвращает оригинальная функция.

Далее рассмотрим итоговый код:

typescript
1type AnyFunction = (...args: any[]) => any;
2type MemoizedFunction<T extends AnyFunction> = 
3  (...args: Parameters<T>) => ReturnType<T>
4
5function memoize<T extends AnyFunction>(func: T): MemoizedFunction {
6    const cache = new Map<string, ReturnType<T>>();
7    
8    return function(...args: Parameters<T>): ReturnType<T> {
9        const key = JSON.stringify(args);
10        
11        if (!cache.has(key)) {
12            cache.set(key, func(...args));
13        }
14        
15        return cache.get(key)!;
16    };
17}
18

Статья была полезной?