Главная » Статьи » Распространенные ошибки хуков React

Распространенные ошибки хуков React

Распространенные ошибки хуков React

От автора: функция React Hook была впервые представлена в обновлении React 16.8 и стала чрезвычайно популярной среди разработчиков благодаря своим возможностям. Она позволяет подключаться к функциям состояния и жизненного цикла React. Иногда это может быть проблематично в сложных вариантах использования. В этой статье я расскажу о 5 распространенных ошибках React Hook, которых следует избегать каждому разработчику при создании надежных приложений.

1. Изменение порядка вызова хуков

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

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

В приведенном ниже примере я создал базовый компонент React, чтобы выбрать человека из выпадающего списка и отобразить данные о нем.

Компонент SelectPerson принимает идентификатор человека, который должен быть получен через свойство prop и сохраняет личные данные в переменной состояния person с помощью хука useEffect().

import { useState, useEffect } from "react"; function SelectPerson({ id }) { if (!id) { return <div>Please Select a Person </div>; } const [person, setPerson] = useState({ name: "", role: "" }); useEffect(() => { const selectPerson = async () => { const response = await fetch(`/api/persons/${id}`); const selectedPerson = await response.json(); setPerson(selectedPerson); }; selectPerson(); }, [id]); return ( <div> <div>Name: {person.name}</div> <div>Description: {person.role}</div> </div> );
} export default function IndexPage() { const [personId, setPersonId] = useState(""); return ( <div> <select onChange={({ target }) => setPersonId(target.value)}> <option value="">Select a person</option> <option value="1">David</option> <option value="2">John</option> <option value="3">Tim</option> </select> <SelectPerson id={personId} /> </div> );
}

На первый взгляд, все выглядит нормально, но в консоли вы получите ошибку, поскольку useState() и useEffect() не доступны при каждом рендеринге.

Распространенные ошибки хуков React

Чтобы этого избежать, вы можете просто переместить оператор return после вызова хуков.

Итак, не забывайте использовать хуки перед любым ранним возвратом на верхнем уровне вашего компонента React.

import { useState, useEffect } from "react"; function SelectPerson({ id }) { const [person, setPerson] = useState({ name: "", role: "" }); useEffect(() => { const selectPerson = async () => { const response = await fetch(`/api/persons/${id}`); const selectedPerson = await response.json(); setPerson(selectedPerson); }; if (id) { selectPerson(); } }, [id]); if (!id) { return 'Please Select a Person'; } return ( <div> <div>Name: {person.name}</div> <div>Description: {person.role}</div> </div> );
} ....
...
...

2. Использование useState, когда повторный рендеринг не требуется

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

Например, предположим, что у нас есть компонент с двумя кнопками. Первая кнопка запускает счетчик, а вторая — делает запрос на основе текущего состояния счетчика.

import { useState } from "react"; function CounterCalc() { const [counter, setCounter] = useState(0); const onClickCounter = () => { setCounter((c) => c + 1); }; const onClickCounterRequest = () => { apiCall(counter); }; return ( <div> <button onClick={onClickCounter}>Counter</button> <button onClick={onClickCounterRequest}>Counter Request</button> </div> );
} export default function IndexPage() { return ( <div> <CounterCalc /> </div> );
}

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

Итак, если вам нужно использовать переменную внутри компонента, которая сохраняет свое состояние при визуализации без запуска повторной визуализации, useRef хук будет лучшим вариантом.

import { useRef } from "react"; function CounterCalc() { const counter = useRef(0); const onClickCounter = () => { counter.current++; }; const onClickCounterRequest = () => { apiCall(counter.current); }; return ( <div> <button onClick={onClickCounter}>Counter</button> <button onClick={onClickCounterRequest}>Counter Request</button> </div> );
} export default function IndexPage() { return ( <div> <CounterCalc /> </div> );
}

3. Обработка действий с помощью useEffect

Хук useEffect один из самых привлекательных способов обработки задач, связанных с изменениями prop или state. Но могут быть ситуации, когда он еще больше усложняет ситуацию. Предположим, есть компонент, который принимает список данных и отображает его в DOM.

import { useState, useEffect } from "react"; function PersonList({ onSuccess }) { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [data, setData] = useState(null); const fetchPersons = () => { setLoading(true); apiCall() .then((res) => setData(res)) .catch((err) => setError(err)) .finally(() => setLoading(false)); }; useEffect(() => { fetchPersons(); }, []); useEffect(() => { if (!loading && !error && data) { onSuccess(); } }, [loading, error, data, onSuccess]); return <div>Person Data: {data}</div>;
} export default function IndexPage() { return ( <div> <PersonList /> </div> );
}

Первый хук useEffect обрабатывает начальный рендер apiCall(). Если все условия выполнены, второй useEffect вызовет функцию onSuccess().

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

Чтобы исправить ситуацию, мы должны переместить функцию onSucces() внутри функции apiCall(), не используя для этого отдельный хук.

import { useState, useEffect } from "react"; function PersonList({ onSuccess }) { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [data, setData] = useState(null); const fetchPersons = () => { setLoading(true); apiCall() .then((fetchedPersons) => { setData(fetchedPersons); onSuccess(); }) .catch((err) => setError(err)) .finally(() => setLoading(false)); }; useEffect(() => { fetchPersons(); }, []); return <div>Person Data: {data}</div>;
} export default function IndexPage() { return ( <div> <PersonList /> </div> );
}

4. Использование устаревшего состояния

Если вы измените useState несколько раз в последующих строках кода, результаты могут вас удивить.

import { useState } from "react"; function Counter({ onSuccess }) { const [count, setCount] = useState(0); console.log(count); const increaseCount = () => { setCount(count + 1); setCount(count + 1); setCount(count + 1); }; return ( <> <button onClick={increaseCount}>Increase</button> <div>Counter: {count}</div> </> );
} export default function IndexPage() { return ( <div> <Counter /> </div> );
}

В приведенном выше примере компонент Counter увеличивает значение переменной count. Поскольку функция increaseCount() вызывает функцию setCount() 3 раза, вы можете подумать, что одно нажатие кнопки увеличит значение count на 3. Но при каждом нажатии кнопки оно будет увеличиваться только на 1.

Первоначальный вызов setCount(count + 1) увеличивает соответствующее значение count, так как count + 1 = 0 + 1 = 1. Точно так же следующие два вызова setCount(count + 1) также устанавливают count равным 1, поскольку он использует устаревшее состояние count.

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

import { useState } from "react"; function Counter({ onSuccess }) { const [count, setCount] = useState(0); console.log(count); const increaseCount = () => { setCount(count => count + 1); setCount(count => count + 1); setCount(count => count + 1); }; return ( <> <button onClick={increaseCount}>Increase</button> <div>Counter: {count}</div> </> );
} export default function IndexPage() { return ( <div> <Counter /> </div> );
}

5. Отсутствие зависимостей useEffect

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

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

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

useEffect(() => { showCount(count);
}, [count]); // Only re-run the effect if count changes in the preceding render

Рассмотрим следующий пример. На первый взгляд кажется, что это хорошо работающее решение.

import { useState, useEffect} from "react"; function Counter() { const [count, setCount] = useState(0); const showCount = (count) => { console.log("Show Count", count); }; useEffect(() => { showCount(count); }, []); return ( <> <div>Hello..!!</div> <div>Counter: {count}</div> </> );
} export default function IndexPage() { return ( <div> <Counter /> </div> );
}

Но есть проблема. Вы можете увидеть следующее предупреждение в консоли DevTools:

Распространенные ошибки хуков React

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

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

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

import { useState, useEffect} from "react"; function Counter() { const [count, setCount] = useState(0); const showCount = (count) => { console.log("Show Count", count); }; useEffect(() => { showCount(count); }, [count]); return ( <> <div>Hello..!!</div> <div>Counter: {count}</div> </> );
} export default function IndexPage() { return ( <div> <Counter /> </div> );
}

Подведение итогов

React Hooks — это простые и понятные функции многократного использования, которые можно включить в ваш рабочий процесс. Кроме того, они предлагают нам множество вариантов для разнообразного использования и нам не нужно создавать их с нуля.

Но разработчики склонны совершать серьезные ошибки при использовании React Hooks с расширенными пользовательскими функциями. В этой статье я обсудил 5 таких распространенных ошибок, которых следует избегать разработчикам при использовании React Hooks. Спасибо за чтение !!!

Автор: Piumi Liyana Gunawardhana

Источник: blog.bitsrc.io

Редакция: Команда webformyself.

Читайте нас в Telegram, VK, Яндекс.Дзен