[Перевод] Структурирование React-приложений

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

Одна из наиболее приятных возможностей React заключается в том, что эта библиотека не принуждает разработчика к строгому соблюдению неких соглашений, касающихся структуры проекта. Многое в этом плане остаётся на усмотрение программиста. Этот подход отличается от того, который, скажем, принят во фреймворках Ember.js или Angular. Они дают разработчикам больше стандартных возможностей. В этих фреймворках предусмотрены и соглашения, касающиеся структуры проектов, и правила именования файлов и компонентов.

k69yugvbt27p3wtccvzeqrf_v4w.jpeg

Лично мне нравится подход, принятый в React. Дело в том, что я предпочитаю контролировать что-либо сам, не полагаясь на некие «соглашения». Однако много плюсов есть и у того подхода к структурированию проектов, который предлагает тот же Angular. Выбор между свободой и более или менее жёсткими правилами сводится к тому, что именно ближе вам и вашей команде.

За годы работы с React я испробовал множество различных способов структурирования приложений. Некоторые из применённых мной идей оказались более удачными, чем другие. Поэтому здесь я собираюсь рассказать обо всём том, что хорошо показало себя на практике. Я надеюсь, что вы найдёте здесь что-то такое, что пригодится и вам.
Я не стремлюсь показать здесь некий «единственно правильный» способ структурирования приложений. Вы можете взять какие-то мои идеи и изменить их под свои нужды. Вы вполне можете со мной и не согласиться, продолжив работать так, как работали раньше. Разные команды создают разные приложения и используют разные средства для достижения своих целей.

Важно отметить, что если вы заглянете на сайт Thread, в разработке которого я участвую, и посмотрите на устройство его интерфейса, то вы обнаружите там места, в которых те правила, о которых я буду говорить, не соблюдаются. Дело в том, что любые «правила» в программировании стоит воспринимать лишь как рекомендации, а не как всеохватывающие нормативы, которые справедливы в любых ситуациях. И если вы думаете, что какие-то «правила» вам не подходят — вы, ради повышения качества того, над чем работаете, должны находить в себе силу отступать от этих «правил».

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

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


Возможно, вы решите, что рекомендация о том, что не стоит слишком волноваться о правилах, выглядит в начале нашего разговора странно. Но я имею в виду именно это, когда говорю, что главная ошибка, возникающая у программистов в плане соблюдения правил, заключается в том, что программисты придают правилам слишком большое значение. Это особенно справедливо в начале работы над новым проектом. В момент создания первого index.jsx попросту невозможно знать о том, что лучше всего подойдёт для этого проекта. По мере того, как проект будет развиваться, вы совершенно естественным образом выйдете на некую структуру файлов и папок, которая, вероятно, будет весьма неплохо для этого проекта подходить. Если же в ходе продолжения работы окажется, что сложившаяся структура в чём-то неудачна, её можно будет улучшить.

Если вы читаете это и ловите себя на мысли о том, что в вашем приложении нет ничего такого, о чём тут идёт речь, то это — не проблема. Каждое приложение своеобразно, не существует двух абсолютно одинаковых команд разработчиков. Поэтому каждая команда, работая над проектом, приходит к неким соглашениям относительно его структуры и методики работы над ним. Это помогает членам команды трудиться продуктивно. Не стремитесь к тому, чтобы, узнав о том, как кто-то что-то делает, немедленно вводить подобное и у себя. Не пытайтесь внедрить в свою работу то, что названо в неких материалах, да хотя бы и в этом, «наиболее эффективным способом» решения некоей задачи. Я всегда придерживался и придерживаюсь следующей стратегии относительно подобных рекомендаций. У меня есть собственный набор правил, но, читая о том, как в тех или иных ситуациях поступают другие, я выбираю то, что мне кажется удачным и подходящим мне. Подобное приводит к тому, что со временем мои методы работы совершенствуются. При этом у меня не случается никаких потрясений и не возникает желания переписывать всё с нуля.

Важные компоненты размещаются в отдельных папках


Подход к размещению файлов компонентов по папкам, к которому я пришёл, заключается в том, что те компоненты, которые в контексте приложения можно считать «важными», «базовыми», «основными», размещаются в отдельных папках. Эти папки, в свою очередь, размещаются в папке components. Например, если речь идёт о приложении для электронного магазина, то подобным компонентом можно признать компонент , используемый для описания товара. Вот что я имею в виду:

- src/
  - components/
    - product/
      - product.jsx
      - product-price.jsx
    - navigation/
      - navigation.jsx
    - checkout-flow/
      - checkout-flow.jsx


При этом «второстепенные» компоненты, которые используются только некими «основными» компонентами, располагаются в той же папке, что и эти «основные» компоненты. Этот подход хорошо показал себя на практике. Дело в том, что благодаря его применению в проекте появляется некая структура, но уровень вложенности папок оказывается не слишком большим. Его применение не приводит к появлению чего-то вроде ../../../ в командах импорта компонентов, он не затрудняет перемещение по проекту. Этот подход позволяет построить чёткую иерархию компонентов. Тот компонент, имя которого совпадает с именем папки, считается «базовым». Другие компоненты, находящиеся в той же папке, служат цели разделения «базового» компонента на части, что упрощает работу с кодом этого компонента и его поддержку.

Хотя я и являюсь сторонником наличия в проекте некоей структуры папок, я считаю, что самое главное — это подбор хороших имён файлов. Папки, сами по себе, менее важны.

Использование вложенных папок для подкомпонентов


Один из минусов вышеописанного подхода заключается в том, что его применение может привести к появлению папок «базовых» компонентов, содержащих очень много файлов. Рассмотрим, например, компонент . К нему будут прилагаться CSS-файлы (о них мы ещё поговорим), файлы тестов, множество подкомпонентов, и, возможно, другие ресурсы — вроде изображений и SVG-иконок. Этим список «дополнений» не ограничивается. Всё это попадёт в ту же папку, что и «базовый» компонент.

Я, на самом деле, не особенно об этом беспокоюсь. Меня это устраивает в том случае, если файлы имеют продуманные имена, и если их легко можно найти (с помощью средств поиска файлов в редакторе). Если всё так и есть, то структура папок отходит на второй план. Вот твит на эту тему.

Однако если вы предпочитаете, чтобы ваш проект имел бы более разветвлённую структуру, нет ничего сложного в том, чтобы переместить подкомпоненты в их собственные папки:

- src/
  - components/
    - product/
      - product.jsx
      - ...

      - product-price/
        - product-price.jsx


Файлы тестов располагаются там же, где и файлы проверяемых компонентов


Начнём этот раздел с простой рекомендации, которая заключается в том, что файлы тестов стоит размещать там же, где и файлы с кодом, который с их помощью проверяют. Я ещё расскажу о том, как я предпочитаю структурировать компоненты, стремясь к тому, чтобы они находились бы поблизости друг от друга. Но сейчас я могу сказать, что нахожу удобным размещать файлы тестов в тех же папках, что и файлы компонентов. При этом имена файлов с тестами идентичны именам файлов с кодом. К именам тестов, перед расширением имени файла, лишь добавляется суффикс .test:

  • Имя файла компонента: auth.js.
  • Имя файла теста: auth.test.js.


У такого подхода есть несколько сильных сторон:

  • Он облегчает поиск файлов тестов. С одного взгляда можно понять то, существует ли тест для компонента, с которым я работаю.
  • Все необходимые команды импорта оказываются очень простыми. В тесте, для импорта тестируемого кода, не нужно создавать структуры, описывающие, скажем, выход из папки __tests__. Подобные команды выглядят предельно просто. Например — так: import Auth from './auth'.


Если у нас имеются некие данные, используемые в ходе теста, например — нечто вроде моков запросов к API, мы помещаем и их в ту же папку, где уже лежит компонент и его тест. Когда всё, что может понадобиться, лежит в одной папке, это способствует росту продуктивности работы. Например, если используется разветвлённая структура папок и программист уверен в том, что некий файл существует, но не может вспомнить его имя, программисту придётся выискивать этот файл во множестве вложенных директорий. При предлагаемом подходе достаточно взглянуть на содержимое одной папки и всё станет ясно.

CSS-модули


Я — большой поклонник CSS-модулей. Мы обнаружили, что они отлично подходят для создания модульных CSS-правил для компонентов.

Я, кроме того, очень люблю технологию styled-components. Однако в ходе работы над проектами, в которой участвует много разработчиков, оказалось, что наличие в проекте реальных CSS-файлов повышает удобство работы.

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

Более общая рекомендация, суть которой пронизывает весь этот материал, заключается в том, что весь код, имеющий отношение к некоему компоненту, стоит держать в той же папке, в которой находится этот компонент. Прошли те дни, когда отдельные папки использовались для хранения CSS и JS-кода, кода тестов и прочих ресурсов. Использование сложных структур папок усложняет перемещение между файлами и не несёт в себе никакой очевидной пользы за исключением того, что это помогает «организовывать код». Держать в одной и той же папке взаимосвязанные файлы — это значит тратить меньше времени на перемещение между папками в ходе работы.

Мы даже создали Webpack-загрузчик для CSS, возможности которого соответствуют особенностям нашей работы. Он проверяет объявленные имена классов и выдаёт ошибку в консоль в том случае, если мы ссылаемся на несуществующий класс.

Почти всегда в одном файле размещается код лишь одного компонента


Мой опыт показывает, что программисты обычно слишком жёстко придерживаются правила, в соответствии с которым в одном файле должен находиться код одного и только одного React-компонента. При этом я вполне поддерживаю идею, в соответствии с которой не стоит размещать в одном файле слишком много компонентов (только представьте себе сложности именования таких файлов!). Но я полагаю, что нет ничего плохого в том, чтобы поместить в тот же файл, в котором размещён код некоего «большого» компонента, и код «маленького» компонента, связанного с ним. Если подобный ход способствует сохранению чистоты кода, если «маленький» компонент не слишком велик для того, чтобы помещать его в отдельный файл, то это никому не повредит.

Например, если я создаю компонент , и мне нужен маленький фрагмент кода для вывода цены, то я могу поступить так:

const Price = ({ price, currency }) => (
  
    {currency}
    {formatPrice(price)}
  
)

const Product = props => {
  // представьте, что здесь находится большой объём кода!
  return (
    
             
loads more stuff...
    
  ) }


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

Выделение отдельных папок для универсальных компонентов


Мы в последнее время пользуемся универсальными компонентами. Они, фактически, формируют нашу дизайн-систему (которую мы рассчитываем когда-нибудь опубликовать), но пока мы начали с малого — с компонентов вроде