Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как useMemo сверяет зависимости
Это важный вопрос для понимания производительности React. useMemo не сверяет пропсы — он сверяет dependency array.
Основной механизм
const memoizedValue = useMemo(
() => expensiveComputation(a, b),
[a, b] // Dependency array
);
useMemo сравнивает значения в dependency array с предыдущей итерацией, используя Object.is().
Object.is() - это не ===
// Object.is() vs ===
Object.is(NaN, NaN); // true (в отличие от NaN === NaN)
Object.is(0, -0); // false (в отличие от 0 === -0)
Object.is('hello', 'hello'); // true
Object.is({}, {}); // false (разные объекты)
Для примитивных типов Object.is() ведёт себя почти как ===, но для объектов и функций — это сравнение по ссылке.
Практический пример: когда useMemo помогает
function Parent() {
const [count, setCount] = useState(0);
// Проблема: новый объект создаётся при каждом рендере
const userConfig = { name: 'John', age: 30 };
return (
<>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<Child config={userConfig} />
</>
);
}
function Child({ config }) {
console.log('Child rendered with config:', config);
return <div>{config.name}</div>;
}
// Результат: Child рендерится при каждом клике на кнопку
// Потому что userConfig — новый объект каждый раз
Решение с useMemo:
function Parent() {
const [count, setCount] = useState(0);
// useMemo создаёт объект один раз
const userConfig = useMemo(
() => ({ name: 'John', age: 30 }),
[] // Пусто = создать один раз при монтировании
);
return (
<>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<Child config={userConfig} />
</>
);
}
// Теперь Child не рендерится при каждом клике
// Потому что userConfig остаётся тот же объект
Как React сравнивает зависимости
// Упрощённая логика React
function useMemo(fn, dependencies) {
const [prevDeps, setPrevDeps] = useState(null);
const [memoizedValue, setMemoizedValue] = useState(null);
// Проверить, изменились ли зависимости
if (!prevDeps || hasDepenciesChanged(prevDeps, dependencies)) {
// Пересчитать значение
setMemoizedValue(fn());
setPrevDeps(dependencies);
}
return memoizedValue;
}
function hasDepenciesChanged(prevDeps, currentDeps) {
// Сравнить каждую зависимость
for (let i = 0; i < prevDeps.length; i++) {
if (!Object.is(prevDeps[i], currentDeps[i])) {
return true; // Зависимость изменилась
}
}
return false; // Ничего не изменилось
}
Примеры сравнения зависимостей
function Example() {
const [count, setCount] = useState(0);
// Пример 1: Примитивные типы
const value1 = useMemo(() => count * 2, [count]);
// Пересчитывается когда count меняется
// Пример 2: Объекты (опасно!)
const value2 = useMemo(
() => ({ count }), // Новый объект каждый раз
[count] // Это примитив, всё OK
);
// Пример 3: Объект в зависимости (проблема!)
const config = { count }; // Новый объект каждый раз
const value3 = useMemo(
() => expensiveCalc(config),
[config] // Object.is(prevConfig, config) = false
);
// Итог: useMemo не мемоизирует! config меняется каждый раз
// Решение: мемоизировать config
const memoConfig = useMemo(() => ({ count }), [count]);
const value4 = useMemo(
() => expensiveCalc(memoConfig),
[memoConfig]
);
}
Сравнение с React.memo и useCallback
// useMemo - мемоизирует ЗНАЧЕНИЕ
const result = useMemo(() => heavyComputation(), [dependency]);
// useCallback - мемоизирует ФУНКЦИЮ
const handleClick = useCallback(() => {
console.log('clicked');
}, [dependency]);
// React.memo - мемоизирует КОМПОНЕНТ (сравнивает пропсы)
const MemoComponent = React.memo(({ prop }) => {
return <div>{prop}</div>;
});
// Все используют Object.is() для сравнения!
Практический пример: список фильтров
function FilteredList({ items, filterBy }) {
// Проблема: новый отфильтрованный массив создаётся каждый раз
const filtered = items.filter(item => item.type === filterBy);
return (
<List data={filtered} />
);
}
// Решение с useMemo
function FilteredList({ items, filterBy }) {
const filtered = useMemo(
() => items.filter(item => item.type === filterBy),
[items, filterBy] // Пересчитать если items или filterBy изменились
);
return (
<List data={filtered} />
);
}
// Теперь List не рендерится если items и filterBy не изменились
Частая ошибка: неправильная зависимость
// Плохо: зависимость — объект, который меняется каждый раз
function Parent({ userData }) {
const memoUser = useMemo(
() => processUser(userData),
[userData] // userData — новый объект каждый раз?
);
return <Child user={memoUser} />;
}
// Хорошо: зависимость — примитивное значение
function Parent({ userId }) {
const memoUser = useMemo(
() => processUser(userId),
[userId] // userId — string или number
);
return <Child user={memoUser} />;
}
Правило: когда использовать useMemo
// Используй useMemo если:
// 1. Вычисление дорогое
const sorted = useMemo(
() => expensiveSort(largeArray),
[largeArray]
);
// 2. Результат передаётся дочернему компоненту с React.memo
const memoConfig = useMemo(() => ({}), []);
return <MemoChild config={memoConfig} />;
// 3. Результат — зависимость для другого хука
const memoArray = useMemo(() => [a, b], [a, b]);
const effect = useEffect(() => {}, [memoArray]);
// НЕ используй useMemo если:
// 1. Вычисление простое
const doubled = useMemo(() => count * 2, [count]); // Не нужно
// 2. Результат не используется в зависимостях
const user = useMemo(() => fetchUser(id), [id]);
// Если user не передаётся в React.memo компонент
Механизм под капотом
// React хранит для каждого useMemo:
// 1. Предыдущие зависимости [prevDeps]
// 2. Мемоизированное значение [memoizedValue]
// 3. Функцию, создающую значение [fn]
// При рендере:
// if (Object.is(prevDeps[0], currentDeps[0]) &&
// Object.is(prevDeps[1], currentDeps[1]) && ...)
// return memoizedValue; // Вернуть старое значение
// else
// memoizedValue = fn(); // Пересчитать
// prevDeps = currentDeps;
// return memoizedValue;
Заключение
useMemo сверяет каждый элемент массива зависимостей с помощью Object.is(). Для примитивов (number, string, boolean) это работает интуитивно. Для объектов и массивов — сравнение по ссылке. Это часто причина неожиданного поведения, когда useMemo не мемоизирует.