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

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

Чтобы проиллюстрировать, как мы можем этого достичь, я создал репозиторий, который реализует знаменитое приложение TodoMVC, используя шаблон MVP для отделения от фреймворка. Чтобы продемонстрировать, насколько он разделен, он включает три реализации, использующие один и тот же основной код: Angular, React и Vue.

Приношу свои извинения любителям чистого JavaScript, я выбрал Typecript для выполнения этой работы. Недавно я работал с ним, мне нравятся некоторые его преимущества, и он помогает мне следовать объектно-ориентированному подходу. Во всяком случае, подобным образом можно было бы пойти с использованием ванильного JS. И помните, это всего лишь моя точка зрения, а не доказательства;)



Модель-Просмотр-Презентатор

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

Ведущий будет посередине, получая действия пользователя из представления и изменяя данные из модели, или получая данные из модели и форматируя их для представления в представлении.

Но это практическая статья, если вы хотите получить больше теоретических и интересных сведений о MVP или других шаблонах проектирования, вы можете взглянуть на эти статьи Мартина Фаулера, Дерека Грира или Адди Осман i.

Вид

Его единственная обязанность - показать данные, которые отправляет докладчик, и пересылать события действий пользователя докладчику. Для нас это будет интерфейс, который будет определять поведение представления и будет реализован выбранным фреймворком. Хороший подход - создать представление для каждого компонента, в качестве примера мы возьмем компонент «TodoItem» в качестве примера для всего сообщения.

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

Ведущий

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

Все презентаторы приложения расширяют этот базовый класс. Как вы можете видеть, он полностью связывает докладчика с представлением, но это должно работать следующим образом: 1 представление ‹-› 1 докладчик, и каждый докладчик будет управлять только одним компонентом. Давайте посмотрим на презентатора компонента TodoItem:

Как видите, существует более или менее метод для каждого действия пользователя, определенного в представлении, и это вся логика, которую оно должно реализовать. Фактически, мы можем определить абстрактную реализацию представления, которое устанавливает контракт с докладчиком. В моем случае я реализовал его в виде миксина Typescript:

Глубоко заглянув в докладчика, мы тоже не увидим особой логики, потому что я попытался изолировать модель своего приложения в архитектуре, подобной FLUX / REDUX. Я создал свою собственную систему, потому что хотел сделать ее свободной от зависимостей, но вы можете использовать какую-нибудь общую библиотеку, например, саму redux, для ее реализации. Цель использования посредника или диспетчера задач - также изолировать варианты использования приложения от того, как мы их представляем. Точно так же я создал свой собственный инжектор зависимостей, но, возможно, вместо него стоит использовать Inversify или подобную библиотеку.

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

Модель

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

Это простой вариант использования, который должен запускаться, когда пользователь редактирует задачу. Он просто вызывает действие редактирования, определенное в контейнере состояния.

Следующий - еще один пример. В данном случае это не компонент «TodoItem», а компонент «TodoList». Это обработчик, отвечающий за получение видимых задач. Мы можем рассматривать это как «селектор» на языке redux.

Вы можете видеть, что оба обработчика используют некоторые объекты состояния. Эти объекты - это то, что я назвал контейнерами состояния, которые несут ответственность только за изменение состояния (редукторы на языке redux).

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

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

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

Тестирование

Теперь, даже если я еще не написал ни одной строки HTML, я смогу протестировать всю функциональность своего компонента, имитируя представление. Это не сквозной тест, и он не проверяет способ отображения данных, но он охватывает большую часть подверженной ошибкам стороны приложения. Здесь вы можете увидеть небольшой фрагмент файла todo.presenter.spec.ts:

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

Реализация фреймворка

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

Угловой

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

Реагировать

Реализация React имеет некоторые отличия от Angular из-за характера этой структуры. Представление не будет обновлено, пока мы не вызовем метод setState, поэтому наши методы будут делать именно это.

Vue

Реализация в Vue больше похожа на реализацию в Angular. Используя Typescript, мы можем просто обновить свойства класса напрямую, и реактивность обновит наше представление. Самая отличительная черта использования Vue заключается в том, что нам нужно создать компонент промежуточного программного обеспечения для расширения с помощью миксина, потому что именно так Vue работает с Typescript.

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

Мой репозиторий todomvp содержит полную реализацию приложения TodoMVC с тремя фреймворками, а не только компонент TodoItem. Проверьте это, если хотите, и ткните меня в Twitter или по электронной почте, если у вас есть сомнения или если вы обнаружили какую-либо проблему.

Используйте фреймворк и не позволяйте ему себя использовать.

Первоначально опубликовано на https://www.berriart.com 28 мая 2019 г.