Chapters - Table of contents →
Let's review together some real-world examples of usage of Generics and Function overloads from react and lodash.
React
useMemo()
useMemo() can be used as it follows:
1const items = useMemo(
2 () => ({ items: data.filter(/* ... */) }),
3 [data]
4)
5// `items` is of type `{ items: any[] }`
6
7
8// we can also pass a type parameter to add a constraint on the callback
9const items = useMemo<{ items: any }>(
10 // the function must be of type `() => { items: any[] }`
11 () => ({ items: data.filter(/* ... */) }),
12 [data]
13
14
1type DependencyList = ReadonlyArray<any>;
23 * `useMemo` will only recompute the memoized value when one of the `deps` has changed.
4 *
5 * Usage note: if calling `useMemo` with a referentially stable function, also give it as the input in
6 * the second argument.
7 *
8 * ```ts
910 *
1112 * const expensiveResult = useMemo(expensive, [expensive])
13 * return ...
1415 * ```
16 *
171819
20// allow undefined, but don't make it optional as that is very likely a mistake
21function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T;
22
As seen in this Chapter, useMemo<T>() leverages _Generics _in order to infer the return type of the given function as first parameter.
useCallback()
useCallback() can used as it follows:
1useCallback(
2 () => {
3 // do something
4 },
5 []
6)
7
8useCallback(
9 async () => {
10 // do something async
11 },
12 []
13)
14
which is typed by React as follows:
12 * `useCallback` will return a memoized version of the callback that only changes if one of the `inputs`
3 * has changed.
4 *
567
8// TODO (TypeScript 3.0): <T extends (...args: never[]) => unknown>
9function useCallback<
10 T extends (...args: any[]) => any
11>(callback: T, deps: DependencyList): T;
12
useCallback leverages _Generics _with some _type constraints _to ensure that the given callback is a function and, also to infer the return type of the given callback.
lodash
reduce()
Lodash's reduce() is a handy helper when it comes to deal with array manipulation.
Here are some examples:
1[].reduce(
2 (sum, curr) => sum + curr,
3 0
4) // return a `number` (inferred from the second argument)
5
6// return value can be of another type than the input type
7// here `array` to `{ total: number }`
8
9// we need to pass `{ total: number }` to `reduce()` since
10// it cannot infer it from the initial value argument (second argument)
11[].reduce<number, { total: number }>(
12 (acc, curr) => {
13 if (!acc.total) acc.total = 0
14
15 return acc.total = acc.total + curr
16 },
17 {}
18)
19
reduce() type definition is the following:
12 * Reduces a collection to a value which is the accumulated result of running each
3 * element in the collection through the callback, where each successive callback execution
4 * consumes the return value of the previous execution. If accumulator is not provided the
5 * first element of the collection will be used as the initial accumulator value. The callback
6 * is invoked with four arguments: (accumulator, value, index|key, collection).
7891011
12reduce<T, TResult>(
13 collection: T[] | null | undefined,
14 callback: MemoListIterator<T, TResult, T[]>,
15 accumulator: TResult
16): TResult;
17
18// ...
19
20type MemoListIterator<T, TResult, TList> = (
21 prev: TResult,
22 curr: T,
23 index: number,
24 list: TList
25) => TResult;
26
reduce() is a complete example of Generics and _Function overloads _capabilities:
- nesting Generic: MemoListIterator<T, TResult, T[]>
- type constraint on the collection argument: T[]
- inferring types from arguments: TResult and T
get()
Given the following usage:
1let obj: { a: { b: number } } | undefined = { a: { b: 1 } }
2
3get(obj, ['a', 'b']) // => return type is `number | undefined`
4
5get({ a: { b: 1 } }, 'a.b') // => return type is `any`
6
The corresponding lodash types for get() are as follows:
12 * Gets the property value at path of object. If the resolved value is undefined the defaultValue is used
3 * in its place.
4 *
56789
10get<TObject extends object, TKey extends keyof TObject>(
11 object: TObject,
12 path: TKey | [TKey]
13): TObject[TKey];
141516
17get<TObject extends object, TKey extends keyof TObject>(
18 object: TObject | null | undefined,
19 path: TKey | [TKey]
20): TObject[TKey] | undefined;
21
22// ...
23
24get(object: any, path: PropertyPath, defaultValue?: any): any;
25
Simirarly to reduce(), get() is a complete example of Generics and Function overloads capabilities.
1get({ a: { b: { c: 1 } }, [['a', 'b', 'c']]) // return type is `number`!
2