11 ошибок, которых следует избегать при создании React приложений
2 года назад·7 мин. на чтение
Некоторые распространенные ошибки в разработке React приложений, которых следует избегать.
Поскольку React становится все более и более популярным, все больше и больше React разработчиков сталкиваются с различными проблемами в процессе разработки.
В этой статье, мы обобщим некоторые распространенные ошибки в разработке React приложений, чтобы помочь вам их избежать.
Если вы только начинаете использовать React, рекомендуется внимательно ознакомиться с этой статьей. Если вы уже используете React для разработки проектов, также рекомендуется проверить и заполнить пробелы.
Прочитав эту статью, вы узнаете, как избежать эти 11 ошибок React:
Ошибка: При рендеринге списка не используется
Проблема
Когда мы впервые изучали React, мы отображали список следующим образом:
Ошибка: Использование состояния сразу после выполнения
Проблема
Когда мы изменяем данные через
Ошибка: Появление бесконечного цикла при использовании
Проблема
Когда мы напрямую вызываем метод
Приведем 4 случая использования
Ошибка: Отсутствие очистки побочных эффектов в
Проблема
В классовых компонентах мы используем метод жизненного цикла
- При рендеринге списка не используется
key
- Изменение значения состояния прямым присваиванием
- Привязка значения состояния непосредственно к свойству value инпута
- Использование состояния сразу после выполнения
setState
- Появление бесконечного цикла при использовании
useState
+useEffect
- Отсутствие очистки побочных эффектов в
useEffect
- Неправильное использование логических операторов
- Тип пропсов компонента не типизирован
- Передача строк в качестве значений компонентам
- Имя компонента не начинается с заглавной буквы
- Неверная привязка события к элементу
Ошибка: При рендеринге списка не используется key
Проблема
Когда мы впервые изучали React, мы отображали список следующим образом:
После рендеринга консоль выдаст предупреждение, что для элементов списка необходимо указать ключ. Решение Вам просто нужно последовать этой подсказке и добавитьconst items = [ { id: 1, value: 'item1' }, { id: 2, value: 'item2' }, { id: 3, value: 'item3' }, { id: 4, value: 'item4' }, { id: 5, value: 'item5' } ]; const listItems = items.map((item) => { return <li>{item.value}</li> });
key
к каждому элементу:
const items = [ { id: 1, value: ‘item1’ }, { id: 2, value: ‘item2’ }, { id: 3, value: ‘item3’ }, { id: 4, value: ‘item4’ }, { id: 5, value: ‘item5’ } ]; const listItems = items.map((item) => { return <li key={item.id}>{item.value}</li> });
key
помогает React определить, какие элементы были изменены, например, добавлены или удалены. Поэтому нам нужно установить уникальное значение ключа для каждого элемента в массиве.
Для значения ключа лучше всего установить уникальное значение. В приведенном выше примере используется id
. Можно использовать индекс массива, но такой подход не рекомендуется.
Уникальный ключ помогает React следить за изменениями списка - какой элемент удалился или переместился.
Ошибка: Изменение значения состояния прямым присваиванием
Проблема В React нельзя назначать состояние и изменять напрямую, иначе это вызовет проблемы.В этот момент будет выдано предупреждение не изменять состояние напрямую, а использовать// классовый компонент handleChange = () => { this.state.name = "John"; };
setState()
.
Решение
Классовые компоненты могут быть изменены с помощью setState()
, а функциональные компоненты могут быть изменены с помощью useState()
:
// Классовые компоненты: используйте setState() this.setState({ name: "John" }); // Функциональные компоненты:используйте useState() const [name, setName] = useState(""); setName("John");
Ошибка: Привязка значения состояния непосредственно к свойству value инпута
Проблема Когда мы напрямую привязываем значение состояния к свойствуvalue
инпута, мы обнаружим, что независимо от того, что мы вводим в поле ввода, содержимое поля ввода не изменится.
Это связано с тем, что мы используем переменную состояния в качестве значения по умолчанию для присвоения значенияexport default function App() { const [count, setCount] = useState(0); return <input type="text" value={count} />; }
<input>
, а состояние в функциональном компоненте может быть изменено только функцией set*
, возвращаемым useState
. Таким образом, решение также очень простое, просто используйте функцию set*
при изменении. Подробнее о том как работать с инпутом в React можно прочитать в этой статье.
Решение
Просто привяжите событие onChange
к <input>
и измените его, вызвав setCount
:
export default function App() { const [count, setCount] = useState(0); const handleChange= (event) => setCount(event.target.value); return <input type="text" value={count} onChange={handleChange} />; }
Ошибка: Использование состояния сразу после выполнения setState
Проблема
Когда мы изменяем данные через setState()
и сразу же хотим получить новые данные, возникнет ситуация, что возвращаются старые данные:
Это связано с тем, что// Классовые компоненты // инициализация состояния this.state = { name: "John" }; // обновление состояния this.setState({ name: "Hello, John!" }); console.log(this.state.name); // => John
setState()
является асинхронным. Когда setState()
выполняется, реальная операция обновления будет помещена в асинхронную очередь для выполнения, а код, который будет выполняться следующим (т.е. console.log
в примере), выполняется синхронно, поэтому выводимое в консоль состояние не является последним значением.
Решение
Просто передайте последующую операцию, которая будет выполняться как функция, в качестве второго параметра setState()
, эта функция обратного вызова будет выполнена после завершения обновления.
Теперь обновленное значение выводится правильно.this.setState({ name: "Hello, John!" }, () => { console.log(this.state.name); // => Hello, John! });
Ошибка: Появление бесконечного цикла при использовании useState
+ useEffect
Проблема
Когда мы напрямую вызываем метод set*()
, возвращаемый useState()
внутри useEffect()
, и не устанавливаем второй параметр в useEffect()
, мы столкнемся с бесконечным циклом:
После этого можно увидеть, что данные на странице обновляются, и функцияexport default function App() { const [count, setCount] = useState(0); useEffect(() => { setCount(count + 1); }); return <div className="App">{count}</div>; }
useEffect()
вызывается бесконечно, входя в состояние бесконечного цикла.
Решение
Это распространенная проблема неправильного использования useEffect()
. useEffect()
можно рассматривать как комбинацию трех функций жизненного цикла: componentDidMount
, componentDidUpdate
и componentWillUnmount
в классовых компонентах. useEffect(effect, deps)
принимает 2 аргумента:
effect
функция, которая должна выполниться (побочный эффект)deps
массив зависимостей
deps
выполняется функция эффекта. Чтобы изменить метод, вам нужно всего лишь передать []
в качестве второго аргумента useEffect()
:
export default function App() { const [count, setCount] = useState(0); useEffect(() => { setCount(count + 1); }, []); return <div className="App">{count}</div>; }
useEffect
:
- Если второй параметр не передан: при обновлении любого состояния будет запущена функция эффекта
useEffect
.
useEffect(() => { setCount(count + 1); });
- Если второй параметр - это пустой массив: функция эффекта
useEffect
срабатывает только при монтировании и размонтировании.
useEffect(() => { setCount(count + 1); }, []);
- Если второй параметр представляет собой массив с одним значением: функция эффекта
useEffect
будет запускаться только при изменении значения.
useEffect(() => { setCount(count + 1); }, [name]);
- Если второй параметр представляет собой массив c несколькими значениями: функция эффекта
useEffect
будет запускаться при изменении хотя бы одного из значений из списка зависимостей.
useEffect(() => { setCount(count + 1); }, [name, age]);
Ошибка: Отсутствие очистки побочных эффектов в useEffect
Проблема
В классовых компонентах мы используем метод жизненного цикла componentWillUnmount()
для очистки некоторых побочных эффектов, таких как таймеры, слушатели событий и т. д.
Решение
Из функции эффекта useEffect()
может быть возвращена функция очистки, которая аналогична роли метода жизненного цикла componentWillUnmount()
:
useEffect(() => { // ... return () => clearInterval(id); }, [name, age]);
Ошибка: Неправильное использование логических операторов
Проблема В синтаксисе JSX/TSX мы часто используем логические значения для управления отображаемыми элементами, и во многих случаях мы используем оператор&&
для обработки этой логики:
Мы думаем, что в это время страница будет отображать пустой контент, но на самом деле на ней отобразитсяconst count = 0; const Comp = () => count && <h1>Chris1993</h1>;
0
.
Решение
Причина в том, что ложное выражение приводит к тому, что элементы после &&
пропускаются, и будет возвращено значение ложного выражения. Поэтому нужно стараться написать условие оценки как можно более полным, не полагаясь на истинное и ложное логическое значение JavaScript для сравнения:
Теперь страница будет отображать пустой контент, как и ожидается.const count = 0; const Comp = () => count > 0 && <h1>Chris1993</h1>;
Ошибка: Типы просов компонента не типизированы
Проблема Если компоненты, разработанные разными членами команды, не имеют четко определенных типов для просов, то для коллег будет не очевидно, как использовать компоненты, например:Решениеconst UserInfo = (props) => { return ( <div> {props.name} : {props.age} </div> ); };
- Определить типы пропсов компонента, используя TypeScript.
// Классовые компоненты interface AppProps { value: string; } interface AppState { count: number; } class App extends React.Component<AppProps, AppStore> { // ... } // Функциональные компоненты interface AppProps { value?: string; } const App: React.FC<AppProps> = ({ value = "", children }) => { //... };
- Без использования TypeScript типы пропсов могут быть определены с помощью
propTypes
.
const UserInfo = (props) => { return ( <div> {props.name} : {props.age} </div> ); }; UserInfo.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number.isRequired, };
Ошибка: Передача строк в качестве значений компонентам
Проблема Так как React имеет шаблонный синтаксис, очень похожий на HTML, часто бывает так, что числа передаются напрямую компонентам в пропсы, что приводит к неожиданному результату:Сравнение<MyComp count="99"></MyComp>
props.count === 99
в компоненте MyComp
вернет false
.
Решение
Правильный способ должен заключаться в использовании фигурных скобок для передачи пропсов:
Передача строковых просов будет выглядеть следующим образом:<MyComp count={99}></MyComp>
<MyComp count={"99"}></MyComp>
Ошибка: Имя компонента не начинается с заглавной буквы
Проблема Начинающие разработчики часто забывают называть свои компоненты с заглавной буквы. Компоненты, начинающиеся со строчной буквы в JSX/TSX, компилируются в элементы HTML, такие как<div />
для тегов HTML.
Решение Просто измените первую букву на заглавную:class myComponent extends React.component {}
class MyComponent extends React.component {}
Ошибка: Неверная привязка события к элементу в классовых компонентах
ПроблемаПри нажатии на кнопкуimport { Component } from "react"; export default class HelloComponent extends Component { constructor() { super(); this.state = { name: "John", }; } update() { this.setState({ name: "Hello John!" }); } render() { return ( <div> <button onClick={this.update}>update</button> </div> ); } }
update
консоль сообщит об ошибке, что невозможно прочитать свойства undefined
(чтение setState
)
Решение
Это происходит потому, что this
не привязан к тому контексту, который мы ожидаем. Есть несколько решений:
- Привязать контекст в конструкторе при помощи метода
bind
constructor() { super(); this.state = { name: "John" }; this.update = this.update.bind(this); }
- Использовать стрелочные функции
update = () => { this.setState({ name: "Hello John!" }); };
- Привязать прямо в функции рендеринга
<button onClick={this.update.bind(this)}>update</button>
- Использовать стрелочные функции в функции рендеринга (не рекомендуется, т.к. это создает новую функцию каждый раз при рендеринге компонента, что влияет на производительность)
<button onClick={() => this.update()}>update</button>
Рендеринг и фиксация в React
год назад·1 мин. на чтение
Прежде чем ваши компоненты отобразятся на экране, они должны быть обработаны React. Понимание шагов этого процесса поможет вам понять, как выполняется ваш код, и объяснить его поведение.
Содержание туториала по React
Прежде чем ваши компоненты отобразятся на экране, они должны быть обработаны React. Понимание шагов этого процесса поможет вам понять, как выполняется ваш код, и объяснить его поведение.
Представьте, что ваши компоненты — повара на кухне, собирающие вкусные блюда из ингредиентов. В этом сценарии React — это официант, который принимает запросы от клиентов и приносит им их заказы. Этот процесс запроса и обслуживания пользовательского интерфейса состоит из трех этапов:
- Запуск рендера (доставка заказа гостя на кухню)
- Рендер компонента (подготовка заказа на кухне)
- Фиксация (commit) в DOM (размещение заказа на столе)
Шаг 1. Запуск рендеринга
Есть две причины для рендеринга компонента:- Это начальный рендер компонента.
- Состояние компонента (или одного из его предков) было обновлено.
Начальный рендер
Когда ваше приложение запускается, вам нужно запустить первоначальный рендеринг. Фреймворки и песочницы иногда скрывают этот код, но это делается путем вызоваcreateRoot
с целевым узлом DOM, а затем вызова его метода рендеринга с вашим компонентом:
// index.js import Image from './Image.js'; import { createRoot } from 'react-dom/client'; const root = createRoot(document.getElementById('root')); root.render(<Image />);
// Image.js export default function Image() { return <img src="https://example.com/image.jpg" alt="image" />; }
Рендеринг при обновлении состояния
После первоначального рендеринга компонента вы можете запускать дальнейшие рендеры, обновляя его состояние с помощью функцииset
. Обновление состояния вашего компонента автоматически ставит рендеринг в очередь. (Вы можете представить это как гостя ресторана, который заказывает чай, десерт и другие блюда, после того как сделал свой первый заказ, в зависимости от состояния жажды или голода.)
Шаг 2: React визуализирует ваши компоненты
После запуска рендеринга React вызывает ваши компоненты, чтобы выяснить, что отображать на экране. «Рендеринг» — это React, вызывающий ваши компоненты.- При первоначальном рендеринге React вызовет корневой компонент.
- Для последующих рендеров React будет вызывать функциональный компонент, обновление состояния которого инициировало рендеринг.
Gallery()
и Image()
несколько раз:
// Gallery.js export default function Gallery() { return ( <section> <h1>Inspiring Sculptures</h1> <Image /> <Image /> <Image /> </section> ); } function Image() { return <img src="https://example.com/image.jpg" alt="image" />; }
// index.js import Gallery from './Gallery.js'; import { createRoot } from 'react-dom/client'; const root = createRoot(document.getElementById('root')); root.render(<Gallery />);
- Во время первоначального рендеринга React создаст узлы DOM для тегов
<section>
,<h1>
и трех тегов<img>
. - Во время повторного рендеринга React вычислит, какие из их пропсов, если таковые имеются, изменились с момента предыдущего рендеринга. Он ничего не будет делать с этой информацией до следующего шага, фазы фиксации.
- При тех же входах, тот же выход. При одинаковых входных данных компонент всегда должен возвращать один и тот же JSX. (Когда кто-то заказывает салат с помидорами, он не должен получать салат с луком.)
- Он думает только о своих делах. Он не должен изменять какие-либо объекты или переменные, существовавшие до рендеринга. (Один заказ не должен изменять чей-либо другой заказ.)
Оптимизация производительности
Поведение рендеринга по умолчанию всех компонентов, вложенных в обновленный компонент, не является оптимальным для производительности, если обновленный компонент находится очень высоко в дереве. Если вы столкнулись с проблемой производительности, существует несколько способов ее решения, описанных в разделе «Производительность». Не оптимизируйте преждевременно.Шаг 3: React фиксирует изменения в DOM
После рендеринга (вызова) ваших компонентов React изменит DOM.- Для начального рендеринга React будет использовать DOM API
appendChild()
для размещения на экране всех созданных DOM-узлов. - Для повторного рендеринга React применит минимально необходимые операции (рассчитанные во время рендеринга), чтобы привести DOM в соответствие с последним результатом рендеринга.
<input>
, обновив его значение, но текст не исчезнет при повторном рендеринге компонента:
Это работает, потому что на этом последнем шаге React только обновляет содержимое// Clock.js export default function Clock({ time }) { return ( <> <h1>{time}</h1> <input /> </> ); }
<h1>
новым значением времени. Он видит, что <input>
появляется в JSX в том же месте, что и в прошлый раз, поэтому React не касается <input>
или его значения.
Отрисовка браузером
После того, как рендеринг завершен и React обновил DOM, браузер перерисует экран. Хотя этот процесс известен как "рендеринг в браузере", мы будем называть его "отрисовкой", чтобы избежать путаницы в остальной части этого руководства.Резюме
Любое обновление экрана в приложении React происходит в три этапа:- Запуск
- Рендеринг
- Фиксация