[Перевод] Продвинутые хуки в React: всё о UseEffect

В этой статье рассмотрим советы и приёмы, которые помогут более профессионально написать код на React.

508cba98dc540293cd432084cf8eb290.jpg

Что такое useEffect?

useEffect — это хук, который можно использовать для замены некоторых методов жизненного цикла классового компонента. UseEffect используется с функциональными компонентами в следующих случаях:

  • при визуализации компонента (метод componentDidMount в классовом компоненте);

  • при обновлении компонента (метод componentDidUpdated в классовом компоненте);

  • при удалении компонента из DOM (метод componentWillUnmount в классовом компоненте).

 Несколько побочных явлений:  

  • Получение данных;

  • Прямое обновление DOM;

  • Установка заголовка страницы;

  • Работа с setInterval или setTimeout;

  • Измерение ширины, высоты или положения элементов в DOM;

  • Установка или получение значений в локальном хранилище.

  • Подписка на услуги и её отмена

Массив зависимостей useEffect

useEffect принимает два параметра. Первый аргумент — это функция обратного вызова, для которой мы будем выполнять побочные эффекты; другой — массив зависимостей. Второй аргумент является необязательным.

Если мы не передадим второй аргумент, побочный эффект в функции обратного вызова будет запускаться снова при каждой визуализации компонента.

function MyComponent() {
  useEffect(() => {
    // The side effect will run after every render
  })
}

Если мы передаем второй аргумент в виде пустого массива, побочный эффект в функции обратного вызова сработает только один раз при первой визуализации компонента.

function MyComponent() {
  useEffect(() => {
    // This side effect will only run once, after the first render
  }, [])
}

Если мы передаем свойства или значения состояния во втором аргументе, побочный эффект в функции обратного вызова будет выполняться только при изменении значений свойств или переменной состояния.

import { useEffect, useState } from 'react'
function MyComponent({ prop }) {
   const [state, setState] = useState('')
   useEffect(() => {
      // the side effect will only run when the props or state changed
   }, [prop, state])
}

Стоит отметить, что useEffect использует поверхностное (shallow) сравнение значений зависимостей.

  • Функция обратного вызова будет выполняться после каждой визуализации, если нет массива зависимостей.

  • Если есть пустой массив зависимостей, функция обратного вызова будет запущена только один раз после первой визуализации.

  • Если есть массив зависимостей со значениями свойств или переменной состояния, функция обратного вызова будет выполняться только при изменении этих значений.

Функция очистки useEffect

Второй аргумент useEffect позволяет запускать побочные эффекты при монтировании и обновлении компонента. Нам нужно запустить побочные эффекты и тогда, когда компонент размонтирован. Это функция очистки, которая позволяет нам остановить побочные эффекты непосредственно перед размонтированием компонента.

function MyComponent() {
  useEffect(() => {
    // this side effect will run after every render
    return () => {
      // this side effect will run before the component is unmounted
    }
  })
}

Пример реального использования

import { useEffect } from "react"

const Modal = ({ modalContent, closeModal }) => {
    useEffect(() => {
	let timeout = setTimeout(() => closeModal(), 3000)

	return () => clearTimeout(timeout)
    })
    return (
	

{modalContent}

) } export default Modal

Что такое бесконечный цикл в useEffect?

Хотя useEffect используется повсеместно, для его освоения требуется некоторое время, поэтому многие новые пользователи при освоении useEffect могут получить бесконечный цикл. В этом разделе будут описаны распространенные сценарии, которые генерируют бесконечные циклы, и то, как их избежать.

  1. Если не указан массив зависимостей:

function App() {
   const [users, setUsers] = useState([])
   useEffect(() => {
      const getUsers = async () => {
         const {data} = await axios.get("/api/user")
         setUsers(data)
      }
      getUsers()
   }) // without dependency array
}

Проблема и её решение

Пользовательское значение (состояние) изменяется при визуализации компонента. Поскольку состояние изменилось, компонент визуализируется. Поскольку мы не указали массив зависимостей, useEffect снова запускается, и состояние снова меняется.

function App() {
   const [users, setUsers] = useState([])
   useEffect(() => {
      const getUsers = async () => {
         const {data} = await axios.get("/api/user")
         setUsers(data)
      }
      getUsers()
   }, []) // empty dependency array 
}
  1. Если в массиве зависимостей указана функция:

function App() {
    const [count, setCount] = useState(0)

    function getResult() {
	return 2 * 2
    }

    useEffect(() => {
	setCount((count) => count + 1)
    }, [getResult])
    // we have specified a function in the dependency array

    return (
	

value of count: {count}

) } export default App

Проблема и её решение

Мы знаем, что useEffect проводит поверхностные сравнения. Это делается, чтобы проверить, были ли обновлены зависимости. При использовании setCount состояние обновляется при первой визуализации компонента.

Как только состояние обновлено, компонент визуализируется снова. Поскольку getResult — это функция, контрольное значение в памяти воссоздается каждый раз при визуализации компонента, поэтому результат поверхностного сравнения возвращает false. Таким образом, образуется бесконечный цикл.

function App() {
const [count, setCount] = useState(0)

    const getResult = useCallback(() => {
	    return 2 * 2
    }, [])

    useEffect(() => {
	    setCount((count) => count + 1)
    }, [getResult])
    // we have specified a function in the dependency array

    return (
	

value of count: {count}

) } export default App

При использовании useCallback функция getResult запоминается. Это гарантирует, что контрольное значение функции getResult не изменится. Когда useEffect выполняет поверхностное сравнение, он возвращает true, и компонент не визуализируется.

Отметим, что существует множество способов избежать бесконечных циклов в компоненте. В статье рассказали только о нескольких.

Использование функций Async-Await в useEffect

Если мы хотим получать данные с помощью API, нам нужно выполнять асинхронные операции.

Как нам это делать с помощью useEffect?

  1. Создайте асинхронную функцию вне useEffect и вызовите ее в useEffect.

const getPosts = async () => {
 const {data} = await axios.get('api/posts')
 setPosts(data)
}
useEffect(() => {
 getUsers()
}, [])
  1. Создайте асинхронную функцию в useEffect и вызовите ее в useEffect.

useEffect(() => {
const getPosts = async () => {
 const {data} = await axios.get('api/posts')
 setPosts(data)
}
 getUsers()
}, [])
  1. Используйте IIFE (функция-выражение, вызываемая сразу после создания) в useEffect.

useEffect(() => {
  (async () => {
    const {data} = await axios.get('api/posts')
    setPosts(data)
  })()
}, [])

А вот так, делать не надо!

useEffect( async () => {
 const {data} = await axios.get('api/posts')
 setPosts(data) 
}, [])

Советы и рекомендации по эффективному использованию useEffect

Давайте посмотрим на некоторые приемы, которые мы можем использовать в useEffect.

  1. Используйте UseEffect на верхнем уровне.

if(a > b){ 
   useEffect(() => {
     // incorrect usage
   }, [])
}
useEffect(() => {
   if(a > b){ 
     // incorrect usage
   }
}, [])
useEffect(() => {
   if(a < b) return
     // correct usage
}, [])

Не нужно использовать useEffect в условных выражениях, циклах и вложенных функциях.

  1. Используйте useEffect для одной задачи.

Выполняйте только одну задачу с использованием useEffect. Для нескольких задач вы можете использовать несколько useEffect для одного и того же компонента. Разделите работу на части и назначьте useEffect для каждой. Назначение useEffect на короткие и одноцелевые функции предотвращает нежелательную повторную визуализацию и позволяет сохранить код чистым и читабельным.

Заключение

useEffect — очень полезный и широко используемый хук React, который стоит освоить. Когда вы привыкнете к нему, вам захочется использовать его постоянно.

Делитесь своим опытом в комментариях.

© Habrahabr.ru