Что такое useMemo и зачем он нужен?
Итак, представьте, что ваше приложение — это офис, в котором сотрудники постоянно вычисляют одни и те же данные. Например, секретарша каждый раз вручную пересчитывает количество конфет в срок годности на складе, даже если ни одна конфета не исчезла. Звучит как потеря времени, правда? Вот useMemo в React — это ваш «умный складской учёт», который запоминает результаты тяжёлых вычислений, чтобы повторно не выполнять их без необходимости.
useMemo — это хук, который запоминает результат вычислений до тех пор, пока их зависимости (inputs) остаются неизменными. Это позволяет избежать ненужных затрат производительности.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
- Когда использовать? Когда у вас есть дорогостоящие вычисления, которые вы хотите выполнять только при изменении определённых данных.
- Когда не использовать? Если вычисления лёгкие и незначительно влияют на производительность.
Примеры использования useMemo
Пример 1: оптимизация сложных вычислений
Рассмотрим ситуацию, где компонент выполняет тяжёлую операцию (например, фильтрацию большой коллекции данных). Без useMemo эта операция будет перерасчитываться при каждом рендере.
import React, { useMemo, useState } from "react";
interface Product {
id: number;
name: string;
category: string;
}
const ProductList: React.FC<{ products: Product[] }> = ({ products }) => {
const [categoryFilter, setCategoryFilter] = useState<string>("");
// Используем useMemo для мемоизации результата фильтрации
const filteredProducts = useMemo(() => {
console.log("Фильтруем продукты..."); // Лог для демонстрации работы
return products.filter((product) =>
product.category.toLowerCase().includes(categoryFilter.toLowerCase())
);
}, [products, categoryFilter]); // Пересчёт только при изменении products или categoryFilter
return (
<div>
<input
placeholder="Введите категорию"
value={categoryFilter}
onChange={(e) => setCategoryFilter(e.target.value)}
/>
<ul>
{filteredProducts.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
};
Здесь useMemo предотвращает повторную фильтрацию списка продуктов, если products и categoryFilter остались неизменными.
Пример 2: оптимизация рендеринга с помощью мемоизированных вычислений
Сценарий: у вас есть сложный компонент, который зависит от вычислений, и вы хотите избежать его рендера без необходимости.
const ExpensiveCalculationComponent = ({ a, b }: { a: number; b: number }) => {
const result = useMemo(() => {
console.log("Выполняем сложное вычисление...");
return a ** b; // Мощное вычисление :)
}, [a, b]);
return <div>Результат: {result}</div>;
};
const App: React.FC = () => {
const [counter, setCounter] = useState(0);
return (
<div>
<button onClick={() => setCounter(counter + 1)}>Click me</button>
<ExpensiveCalculationComponent a={2} b={10} /
</div>
);
};
Без useMemo результат будет пересчитываться при каждом изменении состояния counter, что неэффективно.
Избежание избыточной мемоизации
Когда не стоит использовать useMemo?
Хотя useMemo звучит как магия (и это почти так), его избыточное использование может привести к обратным эффектам. Если вычисления лёгкие, то стоимость мемоизации может превысить выгоду. Например:
const Component = ({ value }: { value: number }) => {
const squaredValue = useMemo(() => value * value, [value]);
return <div>{squaredValue}</div>;
};
Этот пример излишен: вычисление value * value настолько быстрое, что мемоизация здесь ни к чему.
Как работает типизация значений в useMemo?
Когда вы используете TypeScript, важно правильно объявлять типы для результата, возвращаемого useMemo. Это делается автоматически, но иногда требуется больше контроля.
Удобство автоматической типизации
React автоматически выводит тип результата useMemo на основе возвращаемого значения:
const memoizedValue = useMemo(() => {
return { name: "React", version: 18 };
}, []);
// memoizedValue: { name: string; version: number }
Явное указание типа
Если вы хотите явно указать тип, это также легко сделать:
interface Framework {
name: string;
version: number;
}
const memoizedFramework = useMemo<Framework>(() => {
return { name: "React", version: 18 };
}, []);
Управление зависимостями в useMemo
Всегда ли правильно указывать зависимости?
Обычно React предупреждает нас, если мы забываем указать зависимости, используя правило линтера. Пример неправильного кода:
const memoizedValue = useMemo(() => {
return a * b;
}, []); // Неверно: не указаны зависимости a и b!
Это приведёт к багу: результат будет мемоизирован только один раз, независимо от изменения a или b.
Советы по работе с зависимостями:
Указывайте все зависимости, используемые внутри функции. Если вы используете переменную или функцию внутри функции
useMemo, она должна быть указана в массиве зависимостей.Не забывайте о функциях. Если вы передаёте функцию как зависимость, убедитесь, что она мемоизирована с помощью
useCallback.Внимательно проверяйте замыкания. Замыкания могут привести к багам, когда зависимости не обновляются должным образом.
Пример реального сценария
Давайте объединим всё, чему мы научились, в одном примере. Представим, что мы создаём таблицу с сортировкой и фильтрацией.
import React, { useMemo, useState } from "react";
interface User {
id: number;
name: string;
age: number;
}
const users: User[] = [
{ id: 1, name: "Alice", age: 25 },
{ id: 2, name: "Bob", age: 30 },
{ id: 3, name: "Charlie", age: 35 },
];
const UserTable: React.FC = () => {
const [query, setQuery] = useState<string>("");
const [sortBy, setSortBy] = useState>"name" | "age">("name");
// Мемоизация отфильтрованного списка
const filteredUsers = useMemo(() => {
console.log("Фильтруем пользователей...");
return users.filter((user) =>
user.name.toLowerCase().includes(query.toLowerCase())
);
}, [query]);
// Мемоизация сортировки
const sortedUsers = useMemo(() => {
console.log("Сортируем пользователей...");
return [...filteredUsers].sort((a, b) =>
a[sortBy] > b[sortBy] ? 1 : -1
);
}, [filteredUsers, sortBy]);
return (
<div>
<input
placeholder="Поиск по имени"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value as "name" | "age")}
>
<option value="name">Имя</option>
<option value="age">Возраст</option>
</select>
<ul>
{sortedUsers.map((user) => (
<li key={user.id}>
{user.name} — {user.age} лет
</li>
))}
</ul>
</div>
);
};
Теперь вы вооружены всем необходимым, чтобы использовать useMemo эффективно, оптимизируя вычисления в своих React-приложениях! 😎
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ