11 ошибок, которых следует избегать при создании React приложений

2 года назад·7 мин. на чтение

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

Поскольку React становится все более и более популярным, все больше и больше React разработчиков сталкиваются с различными проблемами в процессе разработки. В этой статье, мы обобщим некоторые распространенные ошибки в разработке React приложений, чтобы помочь вам их избежать. Если вы только начинаете использовать React, рекомендуется внимательно ознакомиться с этой статьей. Если вы уже используете React для разработки проектов, также рекомендуется проверить и заполнить пробелы.
Прочитав эту статью, вы узнаете, как избежать эти 11 ошибок React:
  • При рендеринге списка не используется 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>;
}
Приведем 4 случая использования 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:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));
Теперь, если вы запустите свое приложение React, вы должны увидеть кнопку "Открыть модальное окно". При нажатии на эту кнопку откроется модальное окно с заголовком "Модальное окно" и текстом "Это модальное окно!". Если вы нажмете на кнопку "Закрыть", модальное окно закроется.
Теперь давайте рассмотрим, как создать всплывающие сообщения с использованием порталов. Создайте новый файл 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. Теперь вы можете использовать эти знания в своих проектах, чтобы создавать лучший пользовательский интерфейс.