От автора: в React мы используем useEffect, когда нам нужно что-то сделать после рендеринга компонента или когда нам нужно применить побочные эффекты. Побочным эффектом может быть выборка данных с удаленного сервера, чтение или запись в локальное хранилище, настройка слушателей событий или настройка подписки.
UseEffect() позволяет нам управлять жизненными циклами компонентов внутри функциональных компонентов. Хук useEffect() можно рассматривать как комбинацию componentDidMount, componentDidUpdate и componentWillUnmount.
Однако иногда мы можем столкнуться с проблемами при пересечении жизненного цикла компонента и жизненного цикла побочного эффекта (начало, выполнение, завершение).
Когда побочный эффект завершается, он пытается обновить состояние уже размонтированного компонента. В результате выводится предупреждение React:
Предупреждение об утечке памяти
В этом посте мы рассмотрим, когда появляется указанное выше предупреждение и как правильно очищать побочные эффекты в React, чтобы избежать утечек памяти.
1. Проблема
Сначала воспроизведем проблему. В примере ниже, выводится либо информация о пользователях, либо простой текст приветствия. Список пользователей загружается с помощью запроса на выборку.
App component:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// useEffectApp.js function App() { const [display, setDisplay] = useState("users"); return ( <div className='App'> <button onClick={() => { setDisplay("users"); }}> display users </button> <button onClick={() => { setDisplay("posts"); }}> display hello message </button> <>{display === "users" ? <Users /> : <Hello />}</> </div> ); } |
Hello component:
1 2 3 4 5 6 7 8 9 10 |
// hello.js export default function Hello() { return ( <p> Hello, World !! </p> ); } |
Users component:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// useEffectUsersNotClean.js export default function Users() { const [list, setList] = useState(null); useEffect(() => { (function () { try { fetch(`https://jsonplaceholder.typicode.com/users`) .then((response) => response.json()) .then((json) => setList(json)); } catch (e) { // Handle the error } })(); }); return ( <div> {list === null ? ( <p>Loading users...</p> ) : ( <> {list.map((item) => { return <div key={item.id}>{item.name}</div>; })} </> )} </div> ); }; |
Прежде чем завершится выборка пользователей, нажмите кнопку отображения приветствия, и в консоли появится предупреждающее сообщение.
Причина этого предупреждения в том, что компонент уже размонтирован, но побочный эффект пытается обновить состояние размонтированного компонента.
Решение состоит в том, чтобы отменить любой побочный эффект при отключении компонента, давайте посмотрим, как это сделать в следующем разделе.
2. Очистка
К счастью, useEffect позволяет нам легко устранять побочные эффекты. Когда функция обратного вызова возвращает другую функцию, React будет использовать ее для очистки.
1 2 3 4 5 6 7 8 9 |
useEffect(() => { // the side effect takes place here. return () => { // the cleanup function } // dependencies array }, []) |
2-1. Очистка запросов на выборку
Сначала мы создаем контроллер, который позволяет нам прерывать запросы DOM, затем мы подключаем контроллер к запросу на выборку. И, наконец, функция очистки t прерывает запрос в случае размонтирования компонента.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// useEffectCleanUpFetchRequests.js useEffect(() => { //create a controller let controller = new AbortController(); (async () => { try { const response = await fetch( `https://jsonplaceholder.typicode.com/posts`, { // connect the controller with the fetch request signal: controller.signal, }, ); setList(await response.json()); controller = null; } catch (e) { // Handle the error } })(); //aborts the request when the component umounts return () => controller?.abort(); }); |
2-2. Очистка при обновлении свойств или состояния
Могут возникнуть случаи, когда мы хотим прервать запрос на выборку, когда побочный эффект зависит от свойства или значения состояния. И, как я упоминал ранее, хук useeffect() может обрабатывать эти случаи. Например, рассмотрим следующий компонент User, который получает запрос на загрузку сведений о конкретном сотруднике на основе идентификатора, указанного в реквизитах.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
// UseEffectCleanUpOnPropOrStateUpdate.js export default function User({ id }) { const [user, setUser] = useState(null); useEffect(() => { let controller = new AbortController(); (async () => { try { const response = await fetch( `https://jsonplaceholder.typicode.com/users/${id}`, { signal: controller.signal, }, ); setUser(await response.json()); controller = null; } catch (e) { // Handle the error } })(); // clean up function return () => controller?.abort(); // add a dependency array }, [id]); return ( <div> {user === null ? ( <p>Loading user's data ...</p> ) : ( <div key={user.id}>{user.name}</div> )} </div> ); }; |
2-3. Очистка таймеров
При использовании функций таймера мы можем очистить их при размонтировании с помощью специальной функции clearTimeout (timerId). Например, рассмотрим счетчик, который автоматически увеличивается каждую секунду.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// useEffectCleanUpTimersWhenTheComponentUnmounts.js export default function AutoIncrementaedCounter() { const [counterValue, setCounterValue] = useState(0); useEffect(() => { //increments the counter value by 1 every 3 secends let timerId = setTimeout(() => { setCounterValue(counterValue + 1); timerId = null; }, 3000); // cleanup the timmer when component unmout return () => clearTimeout(timerId); }); return <p>{counterValue}</p>; } |
2–4. Очистка подписки
Возможно, мы захотим настроить подписку на какой-то внешний источник данных. В этом случае важно выполнить очистку при отключении компонента. Например веб-сокеты.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// useEffectCleanUpWebSockets.js export default function Component() { const [url] = useState(""); useEffect(() => { const webSocket = new WebSocket(url); // do stuff here // clean up when component unmount return () => webSocket.close(); },); // ... } |
3. ЗАКЛЮЧЕНИЕ
Некоторые эффекты могут потребовать очистки, чтобы избежать утечки памяти. UseEffect() позволяет нам выполнять различные виды побочных эффектов после рендеринга компонента, а затем очищать их в зависимости от их типа.
Источник: medium.com
Редакция: Команда webformyself.
Читайте нас в Telegram, VK, Яндекс.Дзен