useEffect
[EN]
useEffect
allows us to running side effects at functional component:
Note: If you know about class lifecycle methods, so
useEffect
hook present combine ofcomponentDidMount
andcomponentDidUpdate
methods. So we can tell, thatuseEffect
will running after render.
For more understandable, let look an example of class-based component and functional component, that will doing same things. Also we trying compare it, and finding differences between.
Effects without reset
Example 1:
- class-based component:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
document.title = `Вы нажали ${this.state.count} раз`;
}
componentDidUpdate() {
document.title = `Вы нажали ${this.state.count} раз`;
}
render() {
return (
<div>
<p>Вы нажали {this.state.count} раз</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Нажми на меня
</button>
</div>
);
}
}
Note: We can see, that code are doubling (looks on code
componentDidMount
andcomponentDidUpdate
methods). It’s not good. But we have alternative.
- functional component:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Вы нажали ${count} раз`;
});
return (
<div>
<p>Вы нажали {count} раз</p>
<button onClick={() => setCount(count + 1)}>
Нажми на меня
</button>
</div>
);
}
It’s looks like better.
So what
useEffect
doing? You’re talking to React, that it’s doing something after render, using this hook. React is remember function (i.e. “effect”), that you send and run it after making all changes to the DOM.
Effects with reset
We are looking side effects, that don’t take reset early. However, there are cases, when reset is necessary. As example, we are may to need listener on something external data source. In this case, it’s very important to run reset, so that there is no memory leaks.
Let’s looks examples, that realised it:
- class-based component:
class FriendStatus extends React.Component {
constructor(props) {
super(props);
this.state = { isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
render() {
if (this.state.isOnline === null) {
return 'Загрузка...';
}
return this.state.isOnline ? 'В сети' : 'Не в сети';
}
}
At React class, usually, set listener at
componentDidMount
and reset it atcomponentWillUnmount
.
- functional component
Let’s looks, how this component be view, if it will be writes using hooks.
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Указываем, как сбросить этот эффект:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Загрузка...';
}
return isOnline ? 'В сети' : 'Не в сети';
}
Note: If your effect is return a function, so React run it only, when the time comes to reset the effect.
Advice: performance optimization by skipping effects
In some cases a reset or running effect by every render may to call a problem with performance. At class-based components, we can resolve it to using additional compare prevProps
or prevState
inside componentDidUpdate
:
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
document.title = `Вы нажали ${this.state.count} раз`;
}
}
But it’ll be running inside of class-based component, but not functional component.
But functional component has same thing. It contains inside of useEffect
hook.
You can do so, that React missed call effect, if certain values remain unchanged between subsequent renders.
For doing it, pass the array to useEffect
as second optional argument.
useEffect(() => {
document.title = `Вы нажали ${count} раз`;
}, [count]); // Перезапускать эффект только если count поменялся
Note: In this example, we pass
[count]
as second argument.What is it mean? It’s mean, that if
count
will equal5
and our component re-rendered with same value (count
=5
). React will compare[5]
from previous render and[5]
from next render. As, all array elements remain without changing, so React misses this effect. It’s an optimization of this process.When our variable
count
updates to6
on the next render, React compare the array elements[5]
from previous render and the array elements[6]
from next render. In this case, React runs our effect, because5 !== 6
.If you have some elements in array, React will running pur effect, on the time, when one of the elements will be change.
It’s also work for effect with reset step:
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
}, [props.friend.id]); // Повторно подписаться, только если props.friend.id изменился
[RU]
useEffect
позволяет нам запускать побочные эффекты в функциональном компоненте:
Примечание: Если вы знакомы с классовыми методами жизненного цикла, тогда хук
useEffect
представляет комбинацию методовcomponentDidMount
иcomponentDidUpdate
. Так мы можем сказать, чтоuseEffect
будет запускаться после отрисовки.
Для большего понимания, давайте рассмотрим пример классового и функционального компонентов, которые будут выполять то же самое. Также мы попытаемся сравнить их и найти отличия.
Эффекты без сброса
Пример 1:
- Классовый компонент:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
document.title = `Вы нажали ${this.state.count} раз`;
}
componentDidUpdate() {
document.title = `Вы нажали ${this.state.count} раз`;
}
render() {
return (
<div>
<p>Вы нажали {this.state.count} раз</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Нажми на меня
</button>
</div>
);
}
}
Примечание: Мы можем видеть, что код дублируется (посмотрите на код в методах
componentDidMount
иcomponentDidUpdate
). Это не хорошо. Но у нас есть альтернатива.
- Функциональный компонент:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Вы нажали ${count} раз`;
});
return (
<div>
<p>Вы нажали {count} раз</p>
<button onClick={() => setCount(count + 1)}>
Нажми на меня
</button>
</div>
);
}
Этот пример выглядит лучше.
Так что же делает
useEffect
? Вы говорите React’у, что он будет делать что-то после отрисовки, используя этот хук. React запоминает функцию (т.е. “эффект”), который вы отправили и запускает его после всех соврешенных изменений в DOM.
Эффекты со сбросом
Ранее мы рассмотрели подочные эффекты, которые не требуют сброса. Однако, есть случаи, когда сброс необходим. Например, у нас может быть потребность в налчиии слушателя на какой-то внешний источник данных. В этом случае очень важно запустить сброс, чтобы не было утечек памяти.
Давайте рассмотрим примеры, в которых это реализовано:
- Классовый компонент:
class FriendStatus extends React.Component {
constructor(props) {
super(props);
this.state = { isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
render() {
if (this.state.isOnline === null) {
return 'Загрузка...';
}
return this.state.isOnline ? 'В сети' : 'Не в сети';
}
}
В React классе, обычно, устнавливается слушатель в методе
componentDidMount
, а сбрасывается вcomponentWillUnmount
.
- Функциональный компонент
Давайте посмотрим, как этот компонент выглядит, если он будет написан с использованием хуков.
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Указываем, как сбросить этот эффект:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Загрузка...';
}
return isOnline ? 'В сети' : 'Не в сети';
}
Примечание: Если ваш эффект возвращает функцию, тогда React запустит только её, когда придет время сброса того самого эффекта.
Совет: оптимизация производительности за счет пропуска эффектов
В некоторых случаях сброс или запуск эффекта при каждой отрисовке может вызвать проблемы с производительностью. В классовых компонентах, мы можем решить их, используя дополнительное сравнение prevProps
или prevState
внутри метода componentDidUpdate
:
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
document.title = `Вы нажали ${this.state.count} раз`;
}
}
Но это будет запускаться внутри классового компонента, но никак не в функциональном.
Но функциональный компонент обладает схожей вещью. Она содержится внутри хука useEffect
.
Вы можете сделать так, что React пропустит вызов эффекта, если некоторые значения остались нетронутыми между последующими отрисовками.
Для того, чтобы сделать это, передайте массив в useEffect
в качестве второго необязательного аргумента.
useEffect(() => {
document.title = `Вы нажали ${count} раз`;
}, [count]); // Перезапускать эффект только если count поменялся
Примечание: В этом примере мы передаем
[count]
в качестве второго аргумента.Что это значит? Это значит, что если
count
будет равен5
и у нашего компонента до этого произошла перерисовка с тем же значением (count
=5
). React будет сравнивать значение[5]
из предыдущей отрисовки и значение[5]
из следующей. Так как все элементы массива остались без изменений, React пропустит этот эффект. Это является оптимизацией данного процесса.Когда наша переменная
count
обновится до6
при следующей отрисовке, React сравнит элементы массива[5]
из предыдущей отрисовки и[6]
из следующей. В данном случае, React запустит наш эффект, потому что5 !== 6
.Если у вас есть некоторые элементы в массиве, React будет запускать pur-эффект в то время, когда один из элементов будет изменен.
Это также работает для эффекта с шагом сброса:
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
}, [props.friend.id]); // Повторно подписаться, только если props.friend.id изменился