Хук useEffect

Последнее обновление: 01.04.2022

Хук useEffect позволяет управлять различными сопутствующими действиями в функциональном компоненте или то, что называется "side effects" (побочные эффекты), например, извлечение данных, ручное изменение структуры DOM, использование таймеров, логгирование и т.д.. То есть в useEffect выполняет те действия, которые мы не можем выполнить в основной части функционального компонента. Этот хук фактически служит той же цели, что методы жизненного цикла componentDidMount, componentDidUpdate и componentWillUnmount в классах-компонентах.

В качестве параметра в useEffect() передается функция. При вызове хука useEffect по сути определяется "эффект", который затем применяется в приложении. Когда именно применяется? По умолчанию React применяет эффект после каждого рендеринга, в том числе при первом рендеринге приложения. Причем поскольку подобные эффекты определены внутри компонента, они имеют доступ к объекту props и к состоянию компонента.

Например, изменение структуры DOM через useEffect:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>METANIT.COM</title>
</head>
<body>
    <div id="app"></div>
        
    <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/babel">
     
    function User() {
      const [name, setName] = React.useState("Tom");
	  
	  React.useEffect(() => {    
		// Изменяем заголовок html-страницы   
		document.title = `Привет ${name}`;  
	  });
	  
	  
	  function changeName(event) { 
		setName(event.target.value);  
	 }
      return (
        <div>
          <h3>Имя: {name}</h3>
		  
		  <div>
			<p>Имя: <input type="text" value={name} onChange={changeName} /></p>
		  </div>
        </div>
      );
    }
           
     ReactDOM.createRoot(
        document.getElementById("app")
    )
    .render(
        <User />
    );
    </script>
</body>
</html>

Здесь мы определяем эффект, который изменяет заголовок страницы. Причем в заголовок выводится значение переменной состояния - переменной name. То есть при загрузке страницы мы увидим в ее заголовке "Привет Tom".

Однако поскольку при вводе в текстовое поле мы изменяем значение в переменной name, и соответственно React будет выполнять перерендеринг приложения, то одновременно с этим будет изменяться и заголовок страницы:

Хук useEffect и изменение DOM в React

Ограничение применения эффекта

По умолчанию эффект выполняется при каждом повторном рендеринге на веб-странице, однако мы можем указать, чтобы React не применял эффект, если определенные значения не изменились между с момента последнего рендеринга. Для этого в useEffect в качестве необязательного параметра передается массив аргументов.

Сначала рассмотрим саму проблему применения эффекта. Допустим у нас есть следующая страница:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>METANIT.COM</title>
</head>
<body>
    <div id="app"></div>
         
    <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/babel">
      
    function User() {
      const [name, setName] = React.useState("Tom");
      const [age, setAge] = React.useState(36);
       
      React.useEffect(() => {    
        // Изменяем заголовок html-страницы   
        document.title = `Привет ${name}`;  
		console.log("useEffect");
      });
       
      const changeName = (event) => setName(event.target.value);
      const changeAge =(event) => setAge(event.target.value);
	  
      return (
        <div>
          <h3>Имя: {name}</h3>
          <h3>Возраст: {age}</h3>
           
          <div>
            <p>Имя: <input type="text" value={name} onChange={changeName} /></p>
			<p>Возраст: <input type="number" value={age} onChange={changeAge} /></p>
          </div>
        </div>
      );
    }
            
     ReactDOM.createRoot(
        document.getElementById("app")
    )
    .render(
        <User />
    );
    </script>
</body>
</html>

В данном случае в компоненте определены две переменных состояния: name и age. При этом эффект использует только переменную name. Однако даже если переменная name останется без изменений, но переменная age будет изменена, в этом случае эффект будет повторно срабатывать:

useEffect в React и ограничение

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

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>METANIT.COM</title>
</head>
<body>
    <div id="app"></div>
         
    <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/babel">
      
    function User() {
      const [name, setName] = React.useState("Tom");
      const [age, setAge] = React.useState(36);
       
      React.useEffect(() => {    
        // Изменяем заголовок html-страницы   
        document.title = `Привет ${name}`;  
		console.log("useEffect");
      },
	  [name]); // эффект срабатывает только при изменении name
       
      const changeName = (event) => setName(event.target.value);
      const changeAge =(event) => setAge(event.target.value);
	  
      return (
        <div>
          <h3>Имя: {name}</h3>
          <h3>Возраст: {age}</h3>
           
          <div>
            <p>Имя: <input type="text" value={name} onChange={changeName} /></p>
			<p>Возраст: <input type="number" value={age} onChange={changeAge} /></p>
          </div>
        </div>
      );
    }
            
     ReactDOM.createRoot(
        document.getElementById("app")
    )
    .render(
        <User />
    );
    </script>
</body>
</html>

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

React.useEffect(() => {    
	// Изменяем заголовок html-страницы   
	document.title = `Привет ${name}`;  
	console.log("useEffect");
},
[]); // эффект срабатывает только один раз - при самом первом рендеринге

Очистка ресурсов

Нередко в приложении возникает необходимость подисывается на различные ресурсы, а после окончания работы и отписываться от них. В этом случае мы можем использовать специальную форму хука useEffect():

useEffect(() => {
  // код подписки на ресурс
  return () => {
    // код отписки от ресурса
  };
});

В начале функции хука идет подписка на ресурс, а далее оператор return возвращает функцию, которая выполняет отписку от ресурса.

В качестве примера используем подписку/отписку на событие кнопки:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>METANIT.COM</title>
</head>
<body>
    <div id="app"></div>
	<button id="unmountBtn">Unmount</button>
	
    <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/babel">
      
	const root = ReactDOM.createRoot(
        document.getElementById("app")
    );
    function User() {
      const [name, setName] = React.useState("Tom");
	  
	  const unmount = () =>  root.unmount();
    React.useEffect(() => {    
	  
		const unmountBtn = document.getElementById("unmountBtn");
		
         // подписываемся на событие onclick кнопки unmountBtn
		 unmountBtn.addEventListener("click", unmount);
		 console.log("EventListener added");
		 
		return()=>{
			 // отписываемся от события
			unmountBtn.removeEventListener("click", unmount);
			console.log("EventListener removed");
		}
      },
	  []); // эффект срабатывает только один раз - при самом первом рендеринге
	  
      return (<div>
		<h3>Имя: {name}</h3>
		<p>Имя: <input type="text" value={name} onChange={(e) => setName(e.target.value)} /></p>
		</div>);
    }
            
     
    root.render(
        <User />
    );
    </script>
</body>
</html>

Итак, на странице определеная кнопка с id=unmountBtn, на событие которой мы будем подписываться. В качестве действия, которое будет выполнять кнопка, в компоненте User определена функция unmount(), которая удаляет данный компонент с веб-страницы:

root.unmount();

В хуке useEffect сначала подписываемся на событие "click" кнопки (то есть на событие нажатия):

unmountBtn.addEventListener("click", unmount);

Оператор return возвращает функцию, которая выполняет отписку от события:

unmountBtn.removeEventListener("click", unmount);

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

React.useEffect(() => {    
		//............................
		return()=>{
			//.................
		}
      },
	  []);	// пустой массив - хук выполняется один раз

То есть получится следущее: при загрузке компонент подисывается на событие кнопки. По нажатию на кнопку удаляется компонент. При удалении в хуке происходит удаление подписки - она нам больше не нужна, так как компонент уже удален.

Очистка ресурсов в useEffect в React
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850