Типизация значений, возвращаемых useMemo
Типизация в useMemo проста: TypeScript автоматически выводит тип, если он явно возвращается из вычисляемой функции.
const multiplier: number = 3;
const value = useMemo(() => {
return multiplier * 1000;
}, [multiplier]); // TypeScript сам выводит тип как number
Но иногда вам нужно указать тип явно. Например, если вычисляемая функция производит сложные манипуляции с данными:
const cachedUsers = useMemo<{
id: string;
name: string;
}[]>(() => {
return fetchUsers().filter((user) => user.isActive);
}, [dependency]);
Здесь <{ id: string; name: string; }[]> гарантирует, что результат будет строго массивом объектов с заданной структурой.
Использование интерфейсов для типизации
Масштабируемость и читаемость кода — наше всё. Вместо того, чтобы вручную описывать типы, создавайте интерфейсы:
interface User {
id: string;
name: string;
isActive: boolean;
}
const activeUsers = useMemo<User[]>(() => {
return fetchUsers().filter((user) => user.isActive);
}, [dependency]);
Это особенно полезно, если типы используются в нескольких местах. Почему бы не дать TypeScript почувствовать себя важным?
Управление зависимостями в useMemo
Зависимости в useMemo — это массив, содержащий переменные, которые влияют на работу вычисляемой функции.
Пример:
const memoizedValue = useMemo(() => {
return someValue * multiplier;
}, [someValue, multiplier]); // зависимости
Если someValue или multiplier изменяются, функция внутри useMemo вызывается заново. Здесь нужно быть внимательным, иначе можно или пропустить изменения (не добавив переменную в зависимости), или вызвать лишние рендеры (добавив что-то лишнее).
Типичные ошибки
Пропуск зависимости
Если вы забыли добавить переменную в зависимости, мемоизированное значение не обновится при её изменении.
const memoizedValue = useMemo(() => {
return someValue * multiplier;
}, [multiplier]); // где someValue? ошибка!
TypeScript не может автоматически определить, что вы забыли добавить someValue. Поэтому включите строгий режим ESLint для хуков: react-hooks/exhaustive-deps.
Лишние зависимости
Если добавить что-то ненужное, вы будете пересчитывать мемоизированное значение впустую.
const memoizedValue = useMemo(() => {
return someValue * multiplier;
}, [someValue, multiplier, array]); // array не нужен!
В данном случае array — лишняя зависимость, если она не влияет на вычисляемое значение.
Как правильно управлять зависимостями
Придерживайтесь принципов:
- Всегда добавляйте переменные, используемые внутри функции, в зависимостях.
- Избегайте передачи объектов или массивов без мемоизации. Их идентификатор меняется при каждом рендере.
Использование мемоизации для зависимостей
Если у вас есть сложный объект/массив, мемоизируйте его, прежде чем использовать его как зависимость.
const memoizedArray = useMemo(() => [1, 2, 3], []); // мемоизируем массив
const memoizedValue = useMemo(() => {
return someValue * multiplier * memoizedArray.length;
}, [someValue, multiplier, memoizedArray]); // OK
Подводные камни зависимостей и их решения
1. Ошибка "react-hooks/exhaustive-deps"
Иногда TypeScript (или ESLint) может выдавать ошибку, требуя добавить ненужные зависимости. Например:
const memoizedValue = useMemo(() => {
return someValue * multiplier;
}, [multiplier]); // ESLint требует добавить someValue
В случае уверенности, что переменная не влияет на результат, вы можете отключить проверку:
// eslint-disable-next-line react-hooks/exhaustive-deps
const memoizedValue = useMemo(() => {
return someValue * multiplier;
}, [multiplier]);
Но используйте это осторожно!
2. Работа с функциями-зависимостями
Если зависимостью является функция (или, что ещё хуже, стрелочная!), создавайте её с помощью useCallback.
const handleClick = useCallback(() => {
console.log('Click!');
}, []); // мемоизация функции
const memoizedValue = useMemo(() => {
return someValue * multiplier * handleClick.length; // OK
}, [someValue, multiplier, handleClick]);
Практический пример: оптимизация таблицы
Предположим, у нас есть сложная таблица с фильтрацией и сортировкой. Мы хотим избежать затратных пересчётов при каждом рендере.
import React, { useMemo } from 'react';
interface Row {
id: string;
name: string;
age: number;
}
const Table: React.FC<{ rows: Row[]; filter: string }> = ({ rows, filter }) => {
const filteredRows = useMemo(() => {
console.log('Фильтрация...');
return rows.filter((row) => row.name.includes(filter));
}, [rows, filter]); // мемоизация на основе rows и filter
return (
<table>
<tbody>
{filteredRows.map((row) => (
<tr key={row.id}>
<td>{row.name}</td>
<td>{row.age}</td>
</tr>
))}
</tbody>
</table>
);
};
- Мы мемоизируем
filteredRows, чтобы не пересчитывать их каждый раз. - Зависимости строго соответствуют переменным, влияющим на результат.
Теперь вы знаете, как типизировать значения в useMemo и правильно управлять зависимостями. Не забывайте, что мемоизация — это мощный инструмент, но его неправильное использование может усложнить отладку и ухудшить производительность. Думайте заранее, и пусть ваш код будет не только красивым, но и быстрым!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ