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

Эта проблема

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

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

Примеры кода в этом посте используют React. Но вы можете столкнуться с теми же проблемами в Angular, AngularJS, Vue, Polymer или любой другой интерфейсной библиотеке на основе компонентов.

Компоненты TabBar передают tabs своему дочернему компоненту. Однако ему не нужны данные для рендеринга собственного представления. В этом примере это не большая проблема. Однако в реальных приложениях у вас будет гораздо больше вложенности. Затем вы просто пропускаете данные через все эти уровни вложенности. Это добавляет шума по всей вашей кодовой базе.

По сути, ваш компонент выполняет несколько задач. Помимо обычной задачи по отображению пользовательского интерфейса, он также передает данные своим потомкам. Избегая этого, мы можем следовать принципу единой ответственности! Компонент либо передает данные дочерним компонентам, либо отображает данные на экране.

Решение

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

Передайте компоненты, а не данные

В приведенном ниже коде компонент TabBar получает Tab компонентов через свойство children. В качестве побочного эффекта от создания TabBar контейнера его теперь можно использовать намного больше. Мы можем перейти к любому дочернему компоненту!

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

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

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

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

Передача нескольких дочерних компонентов

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

Мультибрендовые компоненты

Особая форма повторного использования - это использование одних и тех же компонентов от разных брендов. Если у вас есть глубоко вложенное дерево компонентов и вам нужно изменить один листовой компонент, у вас есть 2 варианта. Либо воссоздайте все дерево, либо сделайте листовой компонент настраиваемым с помощью конструкции if/else.

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

Заключение

Когда вы начинаете создавать свое приложение, в нем не так много вложенности, и код легко читается. Это меняется, когда приложение начинает расти. Вы понимаете, что у вас может возникнуть проблема, когда вы редактируете множество родительских компонентов только для того, чтобы получить нужные данные / обратный вызов. Передавая компоненты вместо данных, вы можете снять с себя ответственность за передачу данных из ваших компонентов. Чем меньше в ваших компонентах, тем легче будет поддерживать и повторно использовать ваш код.

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

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