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>

Использование контекста темы (Theme Context) в React

2 месяца назад·2 мин. на чтение

В этой статье мы покажем, как использовать контекст темы (Theme Context) в React для изменения стилей компонентов.

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

Что такое контекст темы?

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

Шаг 1: Создание контекста темы

Перед тем, как начать использовать контекст темы, нам нужно создать его. Мы будем использовать хук createContext из библиотеки React.
import React from 'react';

const ThemeContext = React.createContext();

Шаг 2: Предоставление значения контекста

Далее мы должны предоставить значение контекста, чтобы он был доступен во всех компонентах, которые находятся внутри провайдера контекста. Мы можем сделать это, обернув нужную часть нашего приложения в компонент ThemeContext.Provider и передав значение через атрибут value.
import React from 'react';

const ThemeContext = React.createContext();

function App() {
  const theme = 'light';

  return (
    <ThemeContext.Provider value={theme}>
      {/* Весь остальной код приложения */}
    </ThemeContext.Provider>
  );
}

Шаг 3: Получение значения контекста

Теперь, когда мы предоставили значение контекста, мы можем получить его в любом компоненте, который находится внутри провайдера контекста. Для функциональных компонентов мы можем использовать хук useContext, чтобы получить значение контекста.
import React, { useContext } from 'react';

const ThemeContext = React.createContext();

function MyComponent() {
  const theme = useContext(ThemeContext);

  return (
    <div>
      Текущая тема: {theme}
    </div>
  );
}
Готово! Теперь компонент MyComponent будет иметь доступ к значению контекста темы.

Преимущества использования контекста темы

Использование контекста темы имеет несколько преимуществ:
  1. Упрощение передачи данных: Контекст темы позволяет избежать необходимости явной передачи данных через каждый компонент в дереве. Это упрощает код и делает его более читабельным.
  2. Гибкость: Контекст темы позволяет легко изменять тему во всем приложении, просто обновив значение контекста. Это особенно полезно при создании переключателя темы.
  3. Легкость использования: Использование контекста темы с помощью хука useContext делает код более компактным и понятным. Он также позволяет избежать использования высокоуровневых компонентов или HOC (Higher-Order Components).

Итоги

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