Сегодня мы переходим часто упускаемых из виду аспектов разработки, это организации кода. Этот урок не о том, как писать JSX или использовать хуки, а о том, как сделать так, чтобы ваш проект не превратился в «спагетти-код», где невозможно ничего найти. Мы разберемся со структурой папок и import/export, которые скрепляют все компоненты в единое целое.
Представьте, что вы строите дом. У вас есть кирпичи, окна, двери, кровля, все необходимые материалы. Если вы свалите их в одну большую кучу посреди участка, то каждый раз, когда вам понадобится цемент или доска, вам придется часами разгребать эту гору в поисках нужного. Точно так же и в программировании. Без четкой структуры ваш проект очень быстро станет нечитаемым, его будет сложно масштабировать, а новые разработчики в команде будут теряться в хаосе файлов.
Хорошая структура проекта, это как продуманный план дома с четко обозначенными комнатами, кладовками и чердаком. Она предсказуема. Вы интуитивно понимаете, где искать компонент кнопки, где лежат утилиты для работы с датами, а где стили для всего приложения. Это не только экономит ваше время, но и делает код профессиональным. Когда вы откроете проект через полгода, вы не будете ломать голову, а просто пойдете в папку components/ui и найдете там нужный вам Button.jsx.
Важно понимать, что не существует единственно правильной «идеальной» структуры. Она эволюционирует вместе с вашим проектом. Структура для небольшого личного блога и для корпоративного приложения с сотнями экранов будет отличаться. Однако есть общепринятые лучшие практики и паттерны, которые мы сегодня и изучим. Мы начнем с простой структуры, подходящей для большинства стартапов и наметим пути для ее масштабирования.
Обзор типичной структуры папок в React-проекте
Давайте рассмотрим структуру, которую создает популярный инструмент create-react-app (CRA) и которую можно взять за основу для своих проектов. Открыв новый проект, вы, скорее всего, увидите нечто подобное:
my-react-app/ ├── public/ │ ├── index.html │ ├── favicon.ico │ └── manifest.json ├── src/ │ ├── components/ │ ├── pages/ │ ├── hooks/ │ ├── utils/ │ ├── styles/ │ ├── assets/ │ ├── App.js │ ├── App.css │ ├── index.js │ └── index.css ├── package.json └── README.md
Теперь давайте пройдемся по каждой папке и файлу, чтобы понять их предназначение.
-
public/. Эта папка содержит статические assets. Самый главный файл здесьindex.html. Это единственный HTML-файл нашего SPA (Single Page Application). В него будет вмонтировано наше React-приложение. Сюда же обычно кладут иконки (favicon.ico),robots.txtи другие файлы, которые должны быть доступны по прямому URL. -
src/. Это сердце нашего приложения. Весь наш JavaScript/JSX код живет здесь.-
components/. Здесь мы храним переиспользуемые UI-компоненты. Это кирпичики нашего интерфейса: кнопки (Button), поля ввода (Input), карточки (Card), заголовки (Header), подвалы (Footer). Внутри эту папку часто структурируют дальше. Например, можно создать подпапкуuiдля базовых компонентов иfeaturesдля более сложных, привязанных к бизнес-логике. -
pages/(илиviews/). Компоненты, которые представляют собой целые страницы приложения:HomePage,AboutPage,ContactPage,UserProfilePage. Каждая страница собирается из множества мелких компонентов, лежащих вcomponents/. -
hooks/. С появлением Hooks в React, стала отличной практикой выносить переиспользуемую логику в кастомные хуки. Например,useLocalStorage,useFetch,useAuth. Они помещаются в эту папку. -
utils/(илиhelpers/). Здесь живут чистые JavaScript-функции-утилиты, не связанные напрямую с React. Форматирование дат, работа с текстом, математические вычисления, все это сюда. -
styles/. Глобальные стили, темы, переменные CSS-in-JS (если вы используете, например, Styled Components) можно хранить здесь. -
assets/. Статические медиа-файлы, специфичные для приложения: изображения, шрифты, SVG-иконки. -
App.js. Корневой компонент нашего приложения. Он обычно содержит маршрутизацию (React Router) и общую layout-разметку (шапку, боковую панель, основную область). -
index.js. Точка входа в приложение. Именно этот файл рендерит наш корневойApp-компонент в DOM-элемент с idroot, который находится вpublic/index.html.
-
По мере роста проекта вы можете добавлять и другие папки: context/ для React Context, services/ или api/ для функций, работающих с бэкендом, constants/ для констант приложения и так далее.
Импорты и экспорты
Если компоненты и утилиты, это органы нашего приложения, то импорты и экспорты, это его кровеносная система. Они позволяют «питательным веществам» (функциям, компонентам, переменным) течь из одного файла в другой. Без них каждый файл был бы изолированным островом и мы не могли бы строить сложные интерфейсы.
В JavaScript и в React существует два основных типа экспортов, именованный (named) и экспорт по умолчанию (default). Давайте разберем их с примерами.
Именованный экспорт (Named Export)
Именованный экспорт используется тогда, когда из одного файла нужно экспортировать несколько сущностей.
Создаем файл src/utils/helpers.js:
// В этом файле мы объявили несколько функций // 1. Именованный экспорт (inline) export const formatDate = (date) => { return new Date(date).toLocaleDateString('ru-RU'); }; // 2. Еще один именованный экспорт (inline) export const capitalizeFirstLetter = (string) => { if (!string) return ''; return string.charAt(0).toUpperCase() + string.slice(1); }; // 3. Объявление функции, а потом ее экспорт const calculateDiscount = (price, discount) => { return price - (price * discount) / 100; }; // Экспорт уже объявленной функции (именованный) export { calculateDiscount };
Как импортировать именованные экспорты в файле src/components/UserCard.js:
// Импорт с использованием тех же имен, что и при экспорте. // Имена должны быть заключены в фигурные скобки {}. import { formatDate, capitalizeFirstLetter } from '../utils/helpers'; // Можно импортировать все именованные экспорты из файла как единый объект // (этот способ используется реже, но знать его полезно) import * as helpers from '../utils/helpers'; // Тогда обращаться к функциям нужно так: helpers.formatDate(...) const UserCard = ({ user }) => { return ( <div className="user-card"> {/* Используем импортированные функции */} <h2>{capitalizeFirstLetter(user.name)}</h2> <p>Зарегистрирован: {formatDate(user.registrationDate)}</p> </div> ); }; export default UserCard;
Ключевые моменты:
-
Имен должно совпадать.
-
Фигурные скобки
{}обязательны. -
Можно переименовать импорт на лету с помощью ключевого слова
as:
import { formatDate as formatRuDate } from '../utils/helpers';
Экспорт по умолчанию (Default Export)
В одном файле может быть только один экспорт по умолчанию. Он обычно используется для экспорта главной сущности файла, чаще всего компонента.
Создаем файл src/components/Button/Button.jsx:
// Компонент кнопки const Button = ({ children, onClick, type = 'button' }) => { return ( <button type={type} className="btn" onClick={onClick}> {children} </button> ); }; // Экспорт по умолчанию. Ключевое слово 'default' export default Button;
Как импортировать экспорт по умолчанию в файле src/pages/HomePage.jsx:
// При импорте экспорта по умолчанию фигурные скобки НЕ нужны. // Мы можем дать ему ЛЮБОЕ имя. Хотя принято давать имя, соответствующее экспортируемой сущности. import Button from '../components/Button/Button'; // Можно было бы написать `import MySuperButton from ...`, но это плохая практика. const HomePage = () => { return ( <div> <h1>Добро пожаловать на главную страницу!</h1> {/* Используем нашу кнопку */} <Button onClick={() => alert('Клик!')}>Нажми на меня</Button> </div> ); }; export default HomePage;
Ключевые моменты:
-
Фигурные скобки не используются.
-
Имя при импорте можно выбрать любое, но лучше использовать стандартное.
-
Экспорт по умолчанию идеально подходит для компонентов и страниц.
Смешанный экспорт
В одном файле можно комбинировать экспорт по умолчанию и именованные экспорты.
Файл src/config/constants.js:
// Именованный экспорт export const API_BASE_URL = 'https://api.mysite.com'; // Еще один именованный экспорт export const APP_NAME = 'Мое Крутое Приложение'; // Экспорт по умолчанию (например, основной конфиг-объект) const mainConfig = { version: '1.0.0', environment: 'production', }; export default mainConfig;
Импорт в другом файле:
// Импортируем экспорт по умолчанию и несколько именованных import mainConfig, { API_BASE_URL, APP_NAME } from '../config/constants'; console.log(APP_NAME); // 'Мое Крутое Приложение' console.log(mainConfig.version); // '1.0.0'
Абсолютные и относительные пути
Когда вы пишете импорты, вы указываете путь к файлу. Существует два типа путей: относительные и абсолютные.
-
Относительные пути. Путь указывается относительно текущего файла.
-
./Component— файлComponentв той же папке. -
../utils/helpers— подняться на одну папку вверх и зайти вutils/helpers. -
../../components/Button— подняться на две папки вверх и зайти вcomponents/Button. -
Проблема. В больших проектах импорты могут превратиться в нечитаемые конструкции вроде
../../../../services/api, в которых легко ошибиться.
-
-
Абсолютные пути. Путь указывается от корня проекта (обычно от папки
src). Это гораздо удобнее.-
components/Button -
pages/HomePage -
utils/helpers
-
Как настроить абсолютные импорты?
В проекте, созданном через create-react-app, это делается очень просто. Нужно создать (или отредактировать) файл jsconfig.json в корне проекта (рядом с package.json).
{ "compilerOptions": { "baseUrl": "src" }, "include": ["src"] }
После этого вы можете переписать ваши «уродливые» относительные импорты на красивые абсолютные:
Было:
import Button from '../../../components/Button/Button';
Стало:
import Button from 'components/Button/Button';
Это делает код чище и уменьшает количество ошибок.
Практические задачи для закрепления
Давайте применим полученные знания на практике. Создадим небольшую структуру и свяжем все компоненты вместе.
Задача 1: Создайте структуру проекта
-
В папке
srcсоздайте папки:components,pages,utils. -
Внутри
componentsсоздайте папкуui. -
Внутри
pagesсоздайте файлыHomePage.jsxиAboutPage.jsx.
Задача 2: Создайте компонент кнопки с экспортом по умолчанию
-
В папке
src/components/uiсоздайте файлButton.jsx. -
Напишите простой функциональный компонент
Button, который принимает пропсыchildrenиonClick. -
Экспортируйте его по умолчанию.
Код для Button.jsx:
const Button = ({ children, onClick }) => { return ( <button onClick={onClick} style={{ padding: '10px 15px', backgroundColor: 'blue', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer' }} > {children} </button> ); }; export default Button;
Задача 3: Создайте утилиты с именованным экспортом
-
В папке
src/utilsсоздайте файлstringHelpers.js. -
Создайте и экспортируйте именованным способом функцию
truncateText(text, maxLength), которая обрезает текст доmaxLengthсимволов и добавляет в конце троеточие.
Код для stringHelpers.js:
export const truncateText = (text, maxLength) => { if (text.length <= maxLength) { return text; } return text.substr(0, maxLength) + '...'; };
Задача 4: Создайте страницу, которая использует и кнопку и утилиту
-
Откройте файл
src/pages/HomePage.jsx. -
Импортируйте компонент
Buttonс помощью абсолютного пути (components/ui/Button). -
Импортируйте функцию
truncateTextс помощью абсолютного пути (utils/stringHelpers). -
Создайте компонент
HomePage, который внутри имеет абзац текста, обрезанный с помощьюtruncateTextи нашу кастомную кнопку. -
Экспортируйте
HomePageпо умолчанию.
Код для HomePage.jsx:
// Используем абсолютные импорты (предварительно настроив jsconfig.json) import Button from 'components/ui/Button'; import { truncateText } from 'utils/stringHelpers'; const HomePage = () => { const longText = "Это очень длинный текст, который определенно нужно обрезать для того, чтобы он поместился в нашем красивом интерфейсе."; return ( <div> <h1>Главная страница</h1> {/* Используем утилиту */} <p>{truncateText(longText, 50)}</p> {/* Используем компонент */} <Button onClick={() => console.log('Кнопка на главной нажата!')}> Я кнопка с главной </Button> </div> ); }; export default HomePage;
Задача 5: Соберите все вместе в App.js
-
Откройте
src/App.js. -
Импортируйте
HomePageс помощью абсолютного пути. -
Отобразите компонент
HomePageвнутриApp.
Код для App.js:
import HomePage from 'pages/HomePage'; import './App.css'; function App() { return ( <div className="App"> <HomePage /> </div> ); } export default App;
Поздравляю! Вы только что создали свою первую осознанно структурированную часть React-приложения. Вы не просто написали код, вы организовали его, сделав понятным и легко расширяемым.
Типичные ошибки и как их избежать
-
Путаница между
export defaultиexport. Помните, для импорта дефолтного экспорта скобки не нужны, для именованного обязательны. Если вы видите ошибкуSyntaxErrorилиundefined, первым делом проверьте правильность экспорта/импорта. -
«Адские» относительные пути. Как только вы видите больше двух
../, самое время задуматься о настройке абсолютных импортов черезjsconfig.json. -
Циклические зависимости. Компонент A импортирует компонент B, который, в свою очередь, импортирует компонент A. React (и JavaScript в целом) может не обработать это корректно, что приведет к ошибкам. Следите за архитектурой ваших компонентов.
-
Неиспользование папок для группировки связанных файлов. Если компонент состоит из самого файла компонента, стилей и тестов, лучше положить его в отдельную папку. Например,
Button/index.jsx,Button/Button.module.css,Button/Button.test.jsx. Тогда импорт останется чистым:import Button from 'components/Button';.
Структура и модульность это то, что отделяет новичка от уверенного разработчика. Потратив немного времени на организацию кода в начале пути, вы сэкономите себе десятки, а то и сотни часов в будущем. Не пренебрегайте этим.
Удачи в изучении и помните, что чистота кода это залог вашего спокойствия в будущем. Чтобы продолжить обучение и погрузиться в следующие темы, такие как состояние, хуки и работа с формами, переходите по ссылке на полный курс с уроками по React для начинающих.
Поддержка автора осуществляется с помощью специальной формы ниже, предоставленной сервисом «ЮMoney». Все платёжные операции выполняются на защищённой странице сервиса, что обеспечивает их корректность и полную безопасность.


