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

Компонентный интерфейс

Компонентная разработка пользовательского интерфейса - это стандарт для разработки веб-приложений. Дерево компонентов представляет все возможные состояния пользовательского интерфейса. Прелесть в том, что вам никогда не придется думать о дереве компонентов. Вы можете сосредоточиться на одном компоненте за раз, а также на компонентах, которые он отображает. Наборы компонентов можно сгруппировать и объединить в пакет для упрощения импорта; однако в некоторых фреймворках компоненты пользовательского интерфейса по-прежнему необходимо импортировать в каждый компонент, в котором они используются (кроме Vue, yay Vue!).

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

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

Инверсия зависимостей в компонентах пользовательского интерфейса

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

Давайте посмотрим, как может выглядеть компонент в приложении React. Я взял этот пример из Документов по Material-UI. Я заменил JSX методом createElement в React. Чтобы убрать шум, я передаю null в качестве реквизита для всех компонентов.

import { createElement as h } from 'react';
import Card from '@material-ui/core/Card';
import CardActionArea from '@material-ui/core/CardActionArea';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import CardMedia from '@material-ui/core/CardMedia';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
function MediaCard (props) {
  return h(Card, null,
    h(CardActionArea, null,
      h(CardMedia, null),
      h(CardContent, null,
        h(Typography, null, 'Lizard')
      )
    ),
    h(CardActions, null,
      h(Button, null, 'Share')
    )
  )
}

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

Давайте посмотрим, как может выглядеть компонент при использовании инверсии зависимостей. Назовем абстракцию «ui-resolver».

import uiResolver from '../ui-resolver'
const h = uiResolver.createElement
function MediaCard (props) {
  return h('card', null,
    h('card-main', null,
      h('card-media', null),
      h('card-content', null,
        h('text', null, 'Lizard')
      )
    ),
    h('card-actions', null,
      h('button', null, 'Share')
    )
  )
}
// optional step
uiResolver.on('media-card', MediaCard)

Во второй версии компонент MediaCard вообще не зависит от библиотеки UI материала. Карточки материального дизайна - это общий шаблон дизайна, который реализован во многих пакетах пользовательского интерфейса. Менее очевидная часть, и вы, возможно, заметили, заключается в том, что теперь наш компонент больше не зависит от React! Мы можем использовать этот компонент «MediaCard» с любой библиотекой компонентов при условии, что мы можем сопоставить аргументы API инфраструктуры компонентов.

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

Давайте посмотрим, как мы это сделаем. Во-первых, соберите все компоненты пользовательского интерфейса материала вместе в виде плагина для ui-resolver.

import { createElement } from 'react';
import Card from '@material-ui/core/Card';
import CardActionArea from '@material-ui/core/CardActionArea';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import CardMedia from '@material-ui/core/CardMedia';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
const h = c => (...args) => createElement(c, ...args)
export default function UiLibrary (uiResolver, options) {
  const { on } = uiResolver

  on('button', h(Button))
  on('card', h(Card))
  on('card-main', h(CardActionArea))
  on('card-actions', h(CardActions))
  on('card-content', h(CardContent))
  on('card-media', h(CardMedia))
  on('button', h(Button))
  on('text', h(Typography))
}

Затем создайте экземпляр ui-resolver и передайте плагин.

import UiResolverLib from 'ui-resolver-lib'
import UiLibrary from './ui-library'
const uiResolver = UiResolverLib()
uiResolver.use(UiLibrary)
export default uiResolver

А как насчет недостатков этого метода? Есть небольшие накладные расходы на вычисления. Вы также не можете использовать JSX (по крайней мере, пока).

Эталонная реализация

Я собрал небольшую эталонную реализацию под названием Ioku (название может измениться). Это 30 строк кода, которые не тестировались, еще не использовались и не рекомендуются для чего-либо, кроме забавных игр. Я начну его повторять, так как у меня будет время определить, можно ли вообще использовать эту идею. Взносы более чем приветствуются, если кто-то еще заинтересован в изучении идеи.