TL; DR: в этой статье мы обсуждаем создание функционального ядра для логики нашего приложения Vue.js. Код доступен по адресу https://github.com/vinicius0026/modularizing-logic-in-vue

Это третья статья из нашей серии «Структурирование больших приложений Vue.js. Вот полный список выпущенных и запланированных статей:

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

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

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

Интерфейсы и функциональные модули вместо классов

Когда мы обсуждали внедрение TypeScript в приложениях Vue.js, мы пошли несколько нетрадиционным путем. Вместо того, чтобы моделировать наши данные вокруг классов, мы определили очень простые интерфейсы для добавления аннотаций типов к нашим данным. Мы использовали только поля, которые составляют наши объекты в интерфейсах - мы еще не упомянули методы или какие-либо операции с данными.

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

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

Планирование функциональности приложения

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

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

Как и в случае с types определениями, нам нужен модульный способ построения этих функций.

Сборка наших модулей

Мы поместим наши модули в каталог modules в папке src. Мы разделим функциональность на столько файлов, сколько это будет целесообразно, сгруппировав связанные функции в отдельные модули.

Начнем с модулей User и Product:

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

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

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

Несмотря на то, что мы объявили интерфейс LineItem в том же файле, что и Invoice, мы будем использовать отдельный файл для модулей Invoice и LineItem. Мы могли бы сгруппировать счет-фактуру и модули позиций с помощью каталога, но пока мы будем держать его простым и понятным. Вы можете использовать любую структуру папок, которая подходит для вашей конкретной ситуации.

Модуль lineItem тоже будет довольно простым:

Теперь перейдем к модулю «Счет-фактура». Это будет более сложный модуль, поэтому мы собираемся убрать функции перед их реализацией.

Разработка модуля Invoice с TDD

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

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

$ vue add unit-jest

Это позаботится об установке и настройке jest для работы в проекте Vue. Напишем несколько тестов для нашего Invoice модуля.

Эти тесты немного длинны, но за ними легко следить. Начнем с того, что наша функция create в модуле invoice вернет пустой счет. Затем мы переходим к тестированию других частей нашего модуля «Счет-фактура». Мы добавили функцию testData, которая помогает создавать объекты, используемые в тестах.

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

Теперь мы должны запустить эти тесты:

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

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

Функция calculateLineTotal очень проста. Он просто умножает ставку на количество. Тем не менее, включение его в отдельную функцию упрощает отслеживание нашего кода и упрощает его изменение.

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

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

И вот наши испытания прошли!

Использование модулей в компоненте Vue

Давайте перепишем наш createInvoice метод в HelloWorld.vue компоненте, чтобы понять, как мы используем наши модули в компоненте.

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

Подведение итогов

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

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

Дайте мне знать, что вы думаете об этом подходе, в комментариях!

Shameless Plug: если вам понравилась эта статья и в вашей компании есть вакансии, в настоящее время я ищу работу старшего инженера полного цикла. Вы можете проверить мой Linkedin и написать мне на vinicius0026 на gmail dot com, если вы считаете, что я вам подхожу. Ваше здоровье! 😃

Первоначально опубликовано на https://viniciusteixeira.tk 13 мая 2020 г.