useEffect
[EN]
useEffect allows us to running side effects at functional component:
Note: If you know about class lifecycle methods, so
useEffecthook present combine ofcomponentDidMountandcomponentDidUpdatemethods. So we can tell, thatuseEffectwill 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
componentDidMountandcomponentDidUpdatemethods). 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
useEffectdoing? 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
componentDidMountand 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
countwill equal5and 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
countupdates to6on 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 изменился