Эта статья посвящена использованию инверсии зависимостей для разделения компонентов представления. Преимущества этого заключаются в следующем: более лаконичные и разборчивые функции рендеринга, сокращение импорта и разделение компонентов.
Компонентный интерфейс
Компонентная разработка пользовательского интерфейса - это стандарт для разработки веб-приложений. Дерево компонентов представляет все возможные состояния пользовательского интерфейса. Прелесть в том, что вам никогда не придется думать о дереве компонентов. Вы можете сосредоточиться на одном компоненте за раз, а также на компонентах, которые он отображает. Наборы компонентов можно сгруппировать и объединить в пакет для упрощения импорта; однако в некоторых фреймворках компоненты пользовательского интерфейса по-прежнему необходимо импортировать в каждый компонент, в котором они используются (кроме 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 строк кода, которые не тестировались, еще не использовались и не рекомендуются для чего-либо, кроме забавных игр. Я начну его повторять, так как у меня будет время определить, можно ли вообще использовать эту идею. Взносы более чем приветствуются, если кто-то еще заинтересован в изучении идеи.