В своем блоге Кевин Шинер поделился об опыте поиска утечек памяти при использовании useCallback. Рассмотрим пример:
import { useState, useCallback } from "react";
class BigObject {
public readonly data = new Uint8Array(1024 * 1024 * 10);
}
export const App = () => {
const [countA, setCountA] = useState(0);
const [countB, setCountB] = useState(0);
const bigData = new BigObject(); // 10MB of data
const handleClickA = useCallback(() => {
setCountA(countA + 1);
}, [countA]);
const handleClickB = useCallback(() => {
setCountB(countB + 1);
}, [countB]);
// …
};
Если попеременно вызывать handleClickA и handleClickB, то произойдет бесконечная утечка памяти. Например, при вызове функции handleClickA будет заново создан колбек только для handleClickA, т.к. поменялся countA. Для handleClickB останется старый колбек с текущей областью видимости. Новая область видимости будет хранить ссылку на новый колбек handleClickA и старый колбек handleClickB, внутри которых старая область видимости компонента.
Основная проблема связана с тем, что в одном компоненте разные useCallback могут ссылаться друг на друга и на другие данные через замыкания. Эти замыкания сохраняются в памяти до тех пор, пока useCallback не будет создан заново. Поэтому наличие в компоненте более одного useCallback затрудняет понимание того, что хранится в памяти.
Для того чтобы избежать утечек памяти в компонентах, придерживайтесь следующих советов:
- Пишите небольшие компоненты и используйте кастомные хуки.
- Избегайте захвата других замыканий. Это может произойти, если вы вызываете из одного useCallback другой useCallback.
- Избегайте мемоизации, когда в этом нет необходимости.
- Используйте useRef для хранения больших объектов.
https://schiener.io/2024-03-03/react-closures