В этом посте мы докажем, что в 90% наших компонентов нам не нужно писать useEffect…

Первый вопрос: когда запускается useEffect?

const User = ({name}) => { // 1
 const [initials, setInitials] = useState(""); // 2
 useEffect(() => {
 setInitials(name.split(" ").map(str => str.charAt(0))); // 4
 }, [name]);
 return (
 <p>{initials}</p> // 3
 );
};

React отобразит этот компонент в следующей последовательности:
1. Прочтите свойство name, допустим, его значение равно "John Doe".
2. Определите состояние с именем initials и присвойте ему пустую строку в качестве начального значения.
3. Верните JSX с этим начальным значением, поэтому initials будет >пустая строка.
3. Запустите функцию, переданную хуку useEffect, который устанавливает имя "John Doe"
4. Верните новый JSX, который теперь имеет инициалы как JD

Таким образом, наш компонент уже имел значение "John Doe" с самого начала, но useEffect заставил его отрисовываться дважды! … Давайте избавимся от этого дополнительного рендера…

const User = ({name}) => { // 1
 const initials = name.split(" ").map(str => str.charAt(0)); // 2
 return (
 <p>{initials}</p> // 3
 );
};

Теперь React
1. Прочитает значение name
2. Вычислит инициалы
3. Вернет JSX с правильное значение, без необходимости повторного рендеринга компонента!

Что, если предыдущий компонент имеет больше рендеров из-за изменений в других свойствах, он будет пересчитывать инициалы в каждом новом рендере, верно? Да, но вместо того, чтобы использовать useEffect и помещать имя в массив зависимостей… Мы можем легко использовать хук useMemo, чтобы не пересчитывать значение при каждом новом рендере!

const User = ({name, age, email}) => { // 1
 const initials = useMemo(() => {
 name.split(" ").map(str => str.charAt(0)); // 2
 }, [name]);
 return (
 <p>{initials}</p> // 3
 );
};

Компонент не будет пересчитывать инициалы при изменении возраста или адреса электронной почты!

Итак, давайте помнить об этом правиле всякий раз, когда мы создаем компонент React…

Все, что может быть вычислено из свойств или состояния, не должно вычисляться внутри useEffect.

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

Есть только один случай, в котором вы должны использовать useEffect. Если вы хотите что-то сделать при монтировании компонента (отображается в первый раз), эта вещь является внешней (выборка API, подписка на событие, применение чего-либо к элементу DOM), просто это не зависит от состояния или реквизита … Так что это будет что-то вроде этого

useEffect(() => {
 const fetchedPosts = fetch('….');
}, [])

Заключение

- useEffect должен быть вашим последним выбором
- useEffect запускается после рендеринга вашего компонента, поэтому, если он внесет какие-либо изменения в состояние, это вызовет дополнительный рендеринг
- Все что угодно которые могут быть рассчитаны из свойств или состояния, не должны рассчитываться внутри useEffect
- Вы можете использовать useMemo для дорогостоящих вычислений
- Вы можете использовать useEffect, только если вы хотите сделать что-то внешнее (Пример: выборка API) при монтировании компонента (только при первом рендеринге)

Спасибо!