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: создание модальных окон и всплывающих сообщений
месяц назад·3 мин. на чтение
В этой статье мы рассмотрим, как использовать порталы в React для создания модальных окон и всплывающих сообщений.
Одной из самых интересных и полезных функциональностей React является работа с порталами. В этой статье мы рассмотрим, как создать модальные окна и всплывающие сообщения с использованием порталов в функциональных компонентах React.
Порталы в React позволяют отрендерить дочерний компонент в другой DOM элемент, который находится вне иерархии компонента. Это может быть полезно, когда требуется рендерить компоненты в других частях страницы или даже вне контейнера приложения.
Для начала, установим все необходимые зависимости. Откройте командную строку и выполните следующую команду:
Теперь создадим компонент, который будет использовать модальное окно. Создайте новый файл
Теперь давайте рассмотрим, как создать всплывающие сообщения с использованием порталов.
Создайте новый файл
Окончив установку, импортируйтеnpm install react-dom
ReactDOM
в файле компонента:
Теперь давайте создадим простой модальный компонент. Создайте новый файлimport ReactDOM from 'react-dom';
Modal.js
и добавьте следующий код:
В этом компоненте мы используемimport React from 'react'; const Modal = ({ isOpen, onClose, children }) => { if (!isOpen) { return null; } return ReactDOM.createPortal( <div className="modal"> <div className="modal-content">{children}</div> <button onClick={onClose}>Закрыть</button> </div>, document.getElementById('modal-root') ); }; export default Modal;
ReactDOM.createPortal
для отображения содержимого модального окна в элементе с идентификатором 'modal-root'
. Помните, что этот элемент должен быть добавлен в HTML файл вашего проекта:
<body> <div id="root"></div> <div id="modal-root"></div> </body>
App.js
и добавьте следующий код:
В этом компоненте мы используемimport React, { useState } from 'react'; import Modal from './Modal'; const App = () => { const [isOpen, setIsOpen] = useState(false); const handleOpenModal = () => { setIsOpen(true); }; const handleCloseModal = () => { setIsOpen(false); }; return ( <div> <button onClick={handleOpenModal}>Открыть модальное окно</button> <Modal isOpen={isOpen} onClose={handleCloseModal}> <h1>Модальное окно</h1> <p>Это модальное окно!</p> </Modal> </div> ); }; export default App;
useState
для создания состояния isOpen
, которое определяет, открыто ли модальное окно или нет. При нажатии на кнопку "Открыть модальное окно"
состояние isOpen
устанавливается в true
, что вызывает рендеринг модального окна. Когда модальное окно закрывается, состояние isOpen
устанавливается в false
.
Теперь, чтобы отобразить наше приложение, импортируйте компонент App
в файл index.js
:
Теперь, если вы запустите свое приложение React, вы должны увидеть кнопкуimport React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.render(<App />, document.getElementById('root'));
"Открыть модальное окно"
. При нажатии на эту кнопку откроется модальное окно с заголовком "Модальное окно"
и текстом "Это модальное окно!"
. Если вы нажмете на кнопку "Закрыть"
, модальное окно закроется.
Toast.js
и добавьте следующий код:
В этом компоненте мы используемimport React from 'react'; import ReactDOM from 'react-dom'; const Toast = ({ message }) => { return ReactDOM.createPortal( <div className="toast">{message}</div>, document.getElementById('toast-root') ); }; export default Toast;
ReactDOM.createPortal
для отображения всплывающего сообщения в элементе с идентификатором 'toast-root'
. Помимо сообщения, мы можем редактировать стили внутри компонента toast
, чтобы обеспечить пользовательскую информацию и удобство использования.
Добавьте следующий код в компонент App.js
, чтобы использовать всплывающее сообщение:
В этом коде мы добавили новое состояниеimport React, { useState } from 'react'; import Modal from './Modal'; import Toast from './Toast'; const App = () => { const [isOpen, setIsOpen] = useState(false); const [showToast, setShowToast] = useState(false); const handleOpenModal = () => { setIsOpen(true); }; const handleCloseModal = () => { setIsOpen(false); }; const handleShowToast = () => { setShowToast(true); setTimeout(() => { setShowToast(false); }, 2000); }; return ( <div> <button onClick={handleOpenModal}>Открыть модальное окно</button> <button onClick={handleShowToast}>Показать всплывающее сообщение</button> <Modal isOpen={isOpen} onClose={handleCloseModal}> <h1>Модальное окно</h1> <p>Это модальное окно!</p> </Modal> {showToast && <Toast message="Привет, я всплывающее сообщение!" />} </div> ); }; export default App;
showToast
, которое определяет, должно ли отображаться всплывающее сообщение или нет. При нажатии на кнопку "Показать всплывающее сообщение"
состояние showToast
становится true
и всплывающее сообщение отображается. Через 2 секунды после отображения сообщение исчезает, так как мы используем setTimeout
в функции handleShowToast
.
Добавьте элемент toast-root
в ваш html-файл, также как и с элементом modal-root
:
Теперь, при запуске приложения и нажатии на кнопку<body> <div id="root"></div> <div id="modal-root"></div> <div id="toast-root"></div> </body>
"Показать всплывающее сообщение"
, вы должны увидеть всплывающее сообщение с текстом "Привет, я всплывающее сообщение!"
. Через 2 секунды оно исчезнет автоматически.
Поздравляю, вы только что освоили работу с порталами React и создали модальные окна и всплывающие сообщения в функциональных компонентах React. Теперь вы можете использовать эти знания в своих проектах, чтобы создавать лучший пользовательский интерфейс.