Часто приходится сталкиваться с решением «Просто добавьте эту опору», страшный момент, который обычно вспоминается при чтении этого компонента с более чем 20 опорами, которые начинались только с 3.

В этой статье исследуется одно возможное решение этой ситуации, связанное с основной идеей написания ваших компонентов, как если бы вы пишете предметно-ориентированный язык⁽¹⁾, нацеленный на проблемное пространство, которое пытается решить ваш компонент.

Начнем с простой задачи: напишите заголовок:

const Header = () => (
  <div className="header-wrapper">
    <div className="header-hamburger" onClick={() => ...}/>
    <img src="http://link-to-logo.png"/>
  </div>
)

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

const Header = ({ menuOnClick, logoURI }) => (
  <div className="header-wrapper">
    <div className="header-humburger" onClick={menuOnClick}" />
    <img src={logoURI}/>
  </div>
)

Тогда самое время предложить еще одну функцию, провести A / B-тестирование или вариант дизайна. По сути, это проблема расширяемости, как мы можем обеспечить стабильный интерфейс для одного продукта в случае, если нам нужно развернуть обновление или исправление ошибок, но в то же время быть открытыми для вмешательства в наше поведение. Хотя мы всегда можем добавить ту или иную опору, как мы можем удержать ее от неупорядоченного роста компонента в результате длинного интерфейса с неизмеримыми опциями.

Давайте напишем тот же компонент, но без каких-либо собственных тегов html, только с использованием других компонентов React, как мы думали о DSL:

const Wrapper = ({ children }) => (
  <div className="header-wrapper">{children}</div>
)
const Menu = ({ onClick }) => (
  <div className="header-hamburger" onClick={menuOnClick}"/>
)
const Logo = ({ uri }) => (
  <img src={logoURI}/>
)
const Header = ({ menuOnClick, logoURI }) => (
  <Wrapper>
    <Menu onClick={menuOnClick}>
    <Logo uri={logoURI}/>
  </Wrapper>
)

Новая особенность? Нет проблем, быстрый способ проверить это - открыть реализацию и использовать ее примитивы. Если ваш код выглядел примерно так:

import Header from 'header-component'
const Page = () => (
  <Header
    menuOnClick={() => ...}
    logoURI={'...'}
  />
  ...
)

Теперь вы можете создать свой собственный заголовок:

import { Wrapper, Menu, Logo } from 'header-component/blocks'
const MyCustomHeader = () => (
  <Wrapper>
    <Menu onClick={() => ...}/>
    <Logo uri={'...'}/>
    <MyABTestingComponent/>
  </Wrapper>
)
const Page = () => (
  <MyCustomHeader/>
  ...
)

Еще лучше, вы можете создать MyCustomHeader.js как независимый узел узла.

Предлагаемый, но уже намеченный способ организации модуля следующий:

header-component\
├── index.js
├── blocks\
│   ├── index.js
│   ├── Wrapper.js
│   ├── Logo.js
│   └── Menu.js
├── Header.js
└── VariantHeader.js

Папка блоков⁽²⁾ содержит только те примитивы, которые экспортированы как атомы, которые будут использоваться в двух доступных наборах: Header и VariantHeader. Любой из ваших блоков или ваших наборов также может иметь более сложную структуру, если это необходимо, например:

header-component\
├── index.js
├── blocks\
│   ├── index.js
│   ├── ...
│   └── Menu\
│       ├── index.js
│       ├── MenuWrapper.js
│       └── MenuIcon.js
├── Header.js
└── VariantHeader\
    ├── index.js
    ├── VariantAddOn.js
    └── VariantHeader.js

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

Следите за своей системой сборки js и проверьте, не влияет ли на производительность увеличивающееся количество узловых модулей⁽³⁾.

Модульность вашего решения может помочь с тестируемостью, ремонтопригодностью или просто посеять ненависть среди ваших товарищей по команде⁽⁴⁾. Кто-то может возразить, что одной из слабых сторон этого подхода является отсутствие четких границ того, как разделить проблемное пространство, но тогда это было бы слабым местом любой предыдущей техники.

Полный и рабочий пример можно найти по адресу https://github.com/aitherios/example-write-react-components-like-a-dsl

  1. Хотя это не совсем DSL, он работает как метафора
  2. Почему бы не назвать папку dsl? Одна из веских причин заключается в том, что вам не нужно объяснять, что такое DSL, своему партнеру по UX или руководству. Будучи более доступным термином, люди обычно понимают, что блок является частью большего набора.
  3. Https://github.com/webpack/webpack/issues/2873
  4. Http://wiki.c2.com/?F flexibleAndExtensible