логотип PurpleSchool
логотип PurpleSchool

Как писать код на React правильно

Автор

Олег Марков

Введение

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

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

Организация проекта и структура директорий

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

Общая структура приложения

Один из популярных стандартов — деление проекта на папки с компонентами, страницами и утилитами. Примерно так:

src/
  components/     // Переиспользуемые компоненты
  pages/          // Отдельные страницы приложения
  hooks/          // Кастомные хуки
  utils/          // Вспомогательные функции
  assets/         // Изображения, иконки, стили
  App.js
  index.js

Каждый компонент (особенно если он крупный и связан с логикой/стилями/тестами) лучше помещать в отдельную папку:

components/
  Button/
    Button.jsx
    Button.module.css
    Button.test.jsx

Такую структуру легко расширять и поддерживать. Если проект использует маршрутизацию (React Router), для страниц удобно использовать отдельную папку pages.

Разделяйте логику и представление

Когда файл компонента разрастается, логику удобно выносить в кастомные хуки (папка hooks) или отдельные утилиты (utils). Это упрощает тестирование и уменьшает дублирование кода.

Смотрите, как можно вынести логику в хук:

// hooks/useUser.js
import { useState, useEffect } from 'react';

export function useUser(id) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // Загружаем пользователя по id
    fetch(`/api/user/${id}`)
      .then(res => res.json())
      .then(setUser);
  }, [id]);

  return user;
}
// components/UserProfile/UserProfile.jsx
import React from 'react';
import { useUser } from '../../hooks/useUser';

function UserProfile({ id }) {
  const user = useUser(id);

  if (!user) return <div>Загрузка...</div>;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>Email: {user.email}</p>
    </div>
  );
}

Теперь логику работы с пользователем можно легко переиспользовать.

Написание чистого, поддерживаемого и эффективного кода является ключевым для успешной разработки React-приложений. Соблюдение лучших практик и использование правильных подходов позволяет создавать более надежные и масштабируемые приложения. Если вы хотите научиться писать код на React правильно и следовать лучшим практикам разработки — приходите на наш большой курс Основы React, React Router и Redux Toolkit. На курсе 177 уроков и 17 упражнений, AI-тренажеры для безлимитной практики с кодом и задачами 24/7, решение задач с живым ревью наставника, еженедельные встречи с менторами.

Создание и организация компонентов

В React каждый элемент интерфейса — это компонент. Старайтесь следовать принципу "одна задача — один компонент": маленькие, переиспользуемые и простые компоненты легче тестировать, поддерживать и развивать.

Функциональные компоненты как стандарт

С приходом хуков большинство новых проектов строят на функциональных компонентах — они проще классовых и легче читаются.

Пример:

// Простой функциональный компонент
function Greeting({ name }) {
  return <p>Привет, {name}!</p>; // Возвращает JSX-разметку
}

Если в компоненте нет сложной логики, пишите его одной строкой:

const Hello = ({ name }) => <span>Привет, {name}!</span>;

Разделяйте умные и глупые компоненты

Умные (container) компоненты отвечают за данные, работу с API, стейт.
Глупые (presentational) компоненты только рендерят то, что получили через props.

Вот так:

// components/UserList/UserList.jsx - умный компонент
import { useEffect, useState } from 'react';
import { UserItem } from './UserItem';

export function UserList() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetch('/api/users').then(r => r.json()).then(setUsers);
  }, []);

  return (
    <ul>
      {users.map(user => <UserItem key={user.id} user={user} />)}
    </ul>
  );
}

// components/UserList/UserItem.jsx - глупый компонент
export function UserItem({ user }) {
  return <li>{user.name}</li>;
}

Такой подход делает код проще для переиспользования: UserItem можно использовать в других местах, даже если данных о пользователях будет мало.

Используйте PropTypes или TypeScript

Чтобы избежать неожиданных ошибок и улучшить подсказки в редакторе, типизируйте пропсы. Можно использовать PropTypes:

import PropTypes from 'prop-types';

function Button({ onClick, children }) {
  return <button onClick={onClick}>{children}</button>;
}

Button.propTypes = {
  onClick: PropTypes.func.isRequired,
  children: PropTypes.node
};

Еще надежнее — использовать TypeScript и объявлять типы пропсов интерфейсами:

type ButtonProps = {
  onClick: () => void;
  children: React.ReactNode;
};

const Button: React.FC<ButtonProps> = ({ onClick, children }) => (
  <button onClick={onClick}>{children}</button>
);

Стейт и управление данными

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

State должен находиться на минимальном необходимом уровне

Старайтесь держать состояние как можно ближе к компоненту, который его использует.

Пример: если список элементов фильтруется внутри компонента списка, держать состояние фильтра стоит именно там.

Если одно и то же состояние нужно нескольким компонентам — выносите его выше в дерево компонентов или используйте контекст.

// State поднимается выше, если нужен нескольким потомкам
function Parent() {
  const [value, setValue] = useState('');

  return (
    <>
      <Input value={value} onChange={setValue} />
      <FilteredList filter={value} />
    </>
  );
}

Используйте только необходимые хуки

useState — для локального состояния компонента.
useReducer — для сложного локального состояния и имитации Redux внутри компонента.
useContext — для передачи общих данных (например, темы оформления) по дереву компонентов, избегая "прокидывания пропсов".

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

// context/ThemeContext.js
import { createContext } from 'react';

export const ThemeContext = createContext('light');
// App.jsx
import { ThemeContext } from './context/ThemeContext';

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}
// components/Toolbar.jsx
import { useContext } from 'react';
import { ThemeContext } from '../context/ThemeContext';

function Toolbar() {
  const theme = useContext(ThemeContext);
  // theme будет "dark"
  return <div className={theme}>Панель инструментов</div>;
}

Не бойтесь внешних библиотек для управления состоянием

Если стейт большой, и его нужно синхронизировать между множеством компонентов (например, корзина магазина, сессии пользователя) — используйте Redux, MobX, Zustand или Recoil. При этом оптимально изолировать доступ только к нужным данным, а не строить "глобальный стор" для всего приложения.

Стилизация компонентов

React не ограничивает вас в выборе подхода к стилизации компонентов. Вот основные способы:

CSS Modules

CSS Modules хорошо подходят для изоляции стилей каждого компонента:

/* Button.module.css */
.button {
  background: #2196f3;
  color: white;
}
import styles from './Button.module.css';

function Button({ children }) {
  return <button className={styles.button}>{children}</button>;
}

Styled-components и аналогичные библиотеки

Эти инструменты позволяют писать CSS прямо в JavaScript-файлах и создавать "стилизованные компоненты".

import styled from 'styled-components';

const Button = styled.button`
  background: #2196f3;
  color: white;
  padding: 10px 20px;
`;

export function MyButton({ children }) {
  return <Button>{children}</Button>;
}

Tailwind CSS

Утилитарный CSS-фреймворк позволяет стилизовать, не выходя из JSX:

function Button({ children }) {
  return (
    <button className="bg-blue-500 text-white px-4 py-2 rounded">
      {children}
    </button>
  );
}

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

Использование эффектов и асинхронных операций

Один из ключевых хуков — useEffect, который управляет побочными эффектами (например, сетевыми запросами, подписками).

Основы использования useEffect

import { useEffect, useState } from 'react';

function DataFetcher() {
  const [data, setData] = useState(null);

  useEffect(() => {
    // Выполнить запрос при монтировании
    fetch('/api/data')
      .then(res => res.json())
      .then(setData);
  }, []); // [] — эффект сработает ТОЛЬКО при первом рендере

  return <div>{data ? JSON.stringify(data) : 'Загрузка...'}</div>;
}

Управляйте зависимостями правильно

Массив зависимостей ([]) в useEffect говорит React, когда запускать эффект. Оставляйте этот массив всегда точным и минимальным — если в нем не хватает переменных, эффект будет "видеть" устаревшие значения.

useEffect(() => {
  // Если использовать `value` внутри эффекта, его стоит добавить в зависимости
  // Иначе useEffect может реагировать на устаревшие value
}, [value]);

Очищайте ресурсы (cleanup)

Если эффект запускает подписку, таймер или сетевой запрос — обязательно очищайте ресурсы после размонтирования компонента.

useEffect(() => {
  const id = setInterval(() => {
    // что-то обновляем
  }, 1000);

  return () => clearInterval(id); // очистка при размонтировании
}, []);

Оптимизация производительности

Реальные приложения могут тормозить, если рендерить компоненты слишком часто или неправильно хранить данные/эффекты.

Мемоизация (React.memo, useMemo, useCallback)

React.memo предотвращает повторный рендер компонента, если его пропсы не изменились.

import React from 'react';

const MyComponent = React.memo(function MyComponent({ value }) {
  // если value не изменяется — компонент не будет перерисован
  return <div>{value}</div>;
});

useMemo — мемоизирует результат функции.

const expensiveValue = useMemo(() => calculateValue(a, b), [a, b]);

useCallback — мемоизирует функцию:

const handleClick = useCallback(() => {
  // действие при клике
}, [value]); // будет создана новая функция только при изменении value

Оптимальное использование ключей (key) при рендере списков

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

<ul>
  {items.map(item => (
    <li key={item.id}>{item.text}</li>
  ))}
</ul>

Lazy loading и code splitting

Для больших проектов используйте React.lazy и динамический импорт.

import React, { Suspense, lazy } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<div>Загрузка...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

Это уменьшает время загрузки страницы.

Форматирование, линтинг и тестирование

Используйте форматтеры и линтеры

Инструменты как Prettier (форматтер) и ESLint (линтер) позволят поддерживать единый стиль кода в команде, ловить ошибки еще до запуска приложения.

Пример .eslintrc.js:

module.exports = {
  extends: ['react-app', 'plugin:react/recommended'],
  rules: {
    // свои правила
  },
};

Покрывайте критичный код тестами

React предоставляет удобные инструменты для тестирования компонентов — например, React Testing Library или Enzyme (устаревающий). Чем проще и меньше компонент — тем легче его покрыть тестом.

import { render, screen } from '@testing-library/react';
import { Button } from './Button';

test('отображает переданный текст', () => {
  render(<Button>Сохранить</Button>);
  expect(screen.getByText('Сохранить')).toBeInTheDocument();
});

Работа с пропсами по умолчанию и значениями по умолчанию

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

function Card({ children, style = {} }) {
  // style будет пустым объектом по умолчанию
  return <div style={style}>{children}</div>;
}

Организация импортов и порядок кода

Всегда размещайте импорты в начале файла. Сначала библиотечные, затем внутренние/пользовательские. Это облегчает навигацию:

import React from 'react'; // стандартные библиотеки
import { useUser } from '../../hooks/useUser'; // внутренние импорты
import styles from './Button.module.css'; // стили

Порядок в файле:

  1. Импорты
  2. Вспомогательные функции
  3. Основной функциональный компонент
  4. Экспорт по умолчанию или именованный экспорт

Использование современных возможностей React

Следите за нововведениями: например, React 18 дает авто-ускорение обновлений, возможность рендерить компоненты асинхронно (useTransition), Suspense для данных. Это позволяет избежать устаревших паттернов.

Заключение

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

Написание качественного кода является основой разработки. Для создания сложных приложений требуется умение управлять состоянием и роутингом. Курс Основы React, React Router и Redux Toolkit поможет вам в этом. В первых 3 модулях уже доступно бесплатное содержание — начните погружаться в основы React уже сегодня.

Частозадаваемые технические вопросы по теме статьи

1. Как добавить глобальный обработчик ошибок в приложение на React?

Добавьте компонент-обертку с использованием класса ErrorBoundary. Пример:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  render() {
    if (this.state.hasError) {
      return <h1>Что-то пошло не так.</h1>;
    }
    return this.props.children;
  }
}
// Использование:
<ErrorBoundary>
  <App />
</ErrorBoundary>

2. Как работать с формами и контролируемыми компонентами в React?

Используйте local state для хранения значений инпутов. Пример:

function MyForm() {
  const [value, setValue] = useState('');
  return (
    <input
      value={value}
      onChange={e => setValue(e.target.value)}
    />
  );
}

3. Что делать, если возникает бесконечный ререндер при использовании useEffect?

Проверьте массив зависимостей во втором аргументе useEffect. Не добавляйте туда функции или объекты, которые пересоздаются при каждом рендере. Мемоизируйте их с помощью useCallback/useMemo или передавайте только необходимые примитивы.

4. Как реализовать переиспользуемые композиции логики между компонентами?

Создайте кастомные хуки. Функции типа useCustomHook могут содержать повторяемую логику, которую затем можно применять в разных компонентах.

5. Как правильно организовать динамические маршруты с React Router?

В файле маршрутов используйте параметры через двоеточие:

<Route path="/user/:id" element={<UserProfile />} />

Внутри компонента используйте хук useParams для получения параметров маршрута:

import { useParams } from 'react-router-dom';
function UserProfile() {
  const { id } = useParams();
  // далее: загрузить пользователя с id
}
Стрелочка влевоЧто такое frontend-разработка на ReactРабота с файлами в ReactСтрелочка вправо

Постройте личный план изучения React до уровня Middle — бесплатно!

React — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по React

Открыть базу знаний

Лучшие курсы по теме

изображение курса

React и Redux Toolkit

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий