Как очистить побочные эффекты в React
UseEffect() позволяет нам управлять жизненными циклами компонентов внутри функциональных компонентов. Хук useEffect() можно рассматривать как комбинацию componentDidMount, componentDidUpdate и componentWillUnmount.
Однако иногда мы можем столкнуться с проблемами при пересечении жизненного цикла компонента и жизненного цикла побочного эффекта (начало, выполнение, завершение).
Когда побочный эффект завершается, он пытается обновить состояние уже размонтированного компонента. В результате выводится предупреждение React:
React JS. Основы
Изучите основы ReactJS на практическом примере по созданию учебного веб-приложения
Получить курс сейчас!
В этом посте мы рассмотрим, когда появляется указанное выше предупреждение и как правильно очищать побочные эффекты в React, чтобы избежать утечек памяти.
1. Проблема
Содержание статьи:
Сначала воспроизведем проблему. В примере ниже, выводится либо информация о пользователях, либо простой текст приветствия. Список пользователей загружается с помощью запроса на выборку.
App component:
JavaScript // 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> ); }
12345678910111213141516171819202122 | // 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:
JavaScript // hello.js export default function Hello() { return ( <p> Hello, World !! </p> ); }
12345678910 | // hello.js export default function Hello() { return ( <p> Hello, World !! </p> );} |
Users component:
JavaScript // 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> ); };
123456789101112131415161718192021222324252627282930 | // 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> );}; |
Прежде чем завершится выборка пользователей, нажмите кнопку отображения приветствия, и в консоли появится предупреждающее сообщение.
Причина этого предупреждения в том, что компонент уже размонтирован, но побочный эффект пытается обновить состояние размонтированного компонента.
React JS. Основы
Изучите основы ReactJS на практическом примере по созданию учебного веб-приложения
Получить курс сейчас!
Решение состоит в том, чтобы отменить любой побочный эффект при отключении компонента, давайте посмотрим, как это сделать в следующем разделе.
2. Очистка
К счастью, useEffect позволяет нам легко устранять побочные эффекты. Когда функция обратного вызова возвращает другую функцию, React будет использовать ее для очистки.
JavaScript useEffect(() => { // the side effect takes place here. return () => { // the cleanup function } // dependencies array }, [])
123456789 | useEffect(() => { // the side effect takes place here. return () => { // the cleanup function } // dependencies array}, []) |
2-1. Очистка запросов на выборку
Сначала мы создаем контроллер, который позволяет нам прерывать запросы DOM, затем мы подключаем контроллер к запросу на выборку. И, наконец, функция очистки t прерывает запрос в случае размонтирования компонента.
JavaScript // 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(); });
1234567891011121314151617181920212223 | // 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, который получает запрос на загрузку сведений о конкретном сотруднике на основе идентификатора, указанного в реквизитах.
JavaScript // 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> ); };
123456789101112131415161718192021222324252627282930313233343536 | // 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). Например, рассмотрим счетчик, который автоматически увеличивается каждую секунду.
JavaScript // 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>; }
12345678910111213141516 | // 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. Очистка подписки
Возможно, мы захотим настроить подписку на какой-то внешний источник данных. В этом случае важно выполнить очистку при отключении компонента. Например веб-сокеты.
JavaScript // 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(); },); // … }
1234567891011121314 | // 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() позволяет нам выполнять различные виды побочных эффектов после рендеринга компонента, а затем очищать их в зависимости от их типа.
Источник: webformyself.com