Заставка - зачем был создан feature-u

Основы feature-u - знакомство с высокоуровневыми концепциями функциональности.

Eatery-nod App - пример приложения для демонстрации функции u

До и после - структура проекта закусочной до и после функций.

Feature-u In Action - исследуйте аспекты функциональности на конкретных примерах.

Feature-u Преимущества - вкратце

Ссылки - тематические статьи.

"Фон"

Начнем с записи моего пути в этом процессе.

У стартовых ворот…

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

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

Препятствия…

Однако оставался ряд препятствий, которые еще предстоит решить ...

Как я могу инкапсулировать и изолировать свои функции, при этом позволяя им взаимодействовать друг с другом?

Как выбранные функции могут вводить инициализацию запуска (даже внедрять утилиту в корневой DOM), не полагаясь на какой-либо внешний процесс запуска?

Как я могу продвигать функциональные компоненты пользовательского интерфейса изолированно и автономно?

Как я могу настроить выбранные мной фреймворки теперь, когда мой код настолько разросся?

Как я могу включить / отключить выбранные функции, которые являются необязательными или требуют обновления лицензии?

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

Цель (что теперь?)

Основная цель функции-u преследует две цели:

  1. Разрешить функциям Plug-and-Play! Это включает в себя множество вещей, например инкапсуляцию, перекрестное взаимодействие, включение, инициализацию и т. Д. В этой статье мы будем опираться на эти концепции.
  2. Автоматизируйте запуск вашего приложения !! У вас есть возможности. Разрешите им продвигать свои характеристики, чтобы центральная утилита могла автоматически настроить платформы, используемые в вашем приложении, тем самым запустив ваше приложение. Эта задача должна выполняться с возможностью расширения, потому что не все используют один и тот же набор фреймворков.

Основы feature-u

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

В свою очередь, эти объекты Feature передаются в launchApp (), который настраивает и запускает ваше приложение. Кроме того, возвращенный объект App экспортируется, чтобы продвигать общедоступный API каждой функции.

аспекты

В feature-u «аспект» - это обобщенный термин, используемый для обозначения различных ингредиентов, которые (в сочетании) составляют ваше приложение.

Аспекты могут принимать разные формы:

  • Компоненты пользовательского интерфейса и маршруты
  • Управление состоянием (действия, редукторы, селекторы)
  • Бизнес-логика
  • Код инициализации запуска
  • И так далее…

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

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

интеграция фреймворка

Фундаментальная цель feature-u - автоматически настроить фреймворк, используемый в вашем стеке времени выполнения (путем накопления необходимых ресурсов для всех ваших функций). Поскольку не все используют одни и те же фреймворки, функция-u выполняет это с помощью расширяемых аспектов (вы можете найти их во внешних пакетах NPM или создать свои собственные).

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

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

Закусочная-кивок

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

eatery-nod случайным образом выбирает ресторан «свидания» из списка фаворитов. У нас с женой постоянные «свидания», и мы всегда не решаемся, в какой из наших любимых ресторанов чаще всего заходить :-) Так что закусочная - это вращающееся колесо!

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

Кроме того, в каждой функции находятся файлы README, описывающие, что выполняет каждая функция. Найдите время и просмотрите эти ресурсы:

  • устройство - инициализирует устройство для использования приложением и продвигает абстракцию API устройства.
  • auth - способствует полной аутентификации пользователя.
  • leftNav - продвигает ящик / боковую панель для конкретного приложения на левой стороне приложения.
  • currentView - поддерживает текущий вид с привязками получения / установки межфункционального взаимодействия.
  • закусочные - управляет и продвигает вид закусочных.
  • обнаружение - управляет и продвигает вид обнаружения.
  • firebase - инициализирует службу Google Firebase.
  • logActions - регистрирует все отправленные действия и их состояние.
  • песочница - продвигает различные интерактивные тесты, используемые в разработке, которые можно легко отключить.

"До после"

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

Давайте посмотрим на структуру каталогов eatery-nod (до и после).

В целях иллюстрации я расширил только несколько каталогов, но думаю, вы поняли идею.

До: вот особенности до моего проекта…

eatery-nod src BEFORE features

src/
├──actions/        ... redux actions
│     auth.js
│     discovery.js
│     eateries.js
│     ... snip snip
├──api/            ... various abstract APIs
│     device.js
│     discovery.js
│     ... snip snip
├──app/            ... mainline startup **1**
│  │  ScreenRouter.js
│  │  SideBar.js
│  │  index.js
│  └──startup/
│     │  createAppStore.js
│     │  platformSetup.android.js
│     │  platformSetup.ios.js
│     └──firebase/
│           firebaseAppConfig.js
│           initFireBase.js
├──appState/       ... redux reducers
│     auth.js
│     discovery.js
│     eateries.js
│     ... snip snip
├──comp/           ... UI Component Screens
│     DiscoveryListScreen.js
│     EateriesListScreen.js
│     ... snip snip
├──logic/          ... redux-logic modules
│     auth.js
│     discovery.js
│     eateries.js
│     ... snip snip
└──util/           ... common utilities

После: а вот после того же проекта…

eatery-nod src AFTER features

src/
│  app.js          ... launches app via launchApp() **2**
├──feature/
│  │  index.js     ... accumulate/promote all app Feature objects
│  ├──auth/        ... the app's authorization feature
│  │  │  actions.js
│  │  │  featureName.js
│  │  │  index.js
│  │  │  logic.js
│  │  │  publicFace.js
│  │  │  route.js
│  │  │  signInFormMeta.js
│  │  │  state.js
│  │  └──comp/
│  │        SignInScreen.js
│  │        SignInVerifyScreen.js
│  ├──currentView/ ... other features
│  ├──device/      ... feature to initialize the device
│  │  │  actions.js
│  │  │  api.js
│  │  │  appDidStart.js
│  │  │  appWillStart.js
│  │  │  featureName.js
│  │  │  index.js
│  │  │  logic.js
│  │  │  publicFace.js
│  │  │  route.js
│  │  │  state.js
│  │  └──init/
│  │        platformSetup.android.js
│  │        platformSetup.ios.js
│  ├──discovery/   ... more features
│  ├──eateries/
│  ├──firebase/
│  ├──leftNav/
│  ├──logActions/
│  └──sandbox/
└──util/           ... common utilities used across all features

Как и ожидалось, разница в организации проекта огромна!

  • Перед функциями - вы найдете конструкции для данной функции, разбросанные по многочисленным типизированным каталогам.
  • После функций: все аспекты данной функции содержатся в ее собственном изолированном каталоге.
  • Заметным отличием является резкое снижение сложности процесса запуска приложения! функции до содержали весь app\ каталог кода запуска (см. **1** выше), а функции после просто содержали один app.js файл запуска (см. **2** выше). Куда делась вся сложность? ... следите за обновлениями!

Feature-u в действии

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

В каждом из следующих разделов кратко представлена ​​новая тема, посвященная функциональным возможностям, в которой приводится пример кода из eatery-nod. Дополнительная информация предоставляется через ссылки как на документацию о функциях, так и на исходный код eatery-nod. В некоторых случаях встроенный пример кода был оптимизирован (чтобы подчеркнуть фокус), однако ссылка с заголовком приведет вас к фактическому коду (размещенному на GitHub).

Вот наши темы…

  1. Упрощенный запуск приложения
  2. Реагирующие платформы
  3. Feature Object
  4. Инициализация функции
  5. Совместная работа с функциями
  6. Интеграция фреймворка
  7. Включение функций
  8. Управляемое расширение кода
  9. Продвижение компонентов пользовательского интерфейса
  10. Единый источник истины

1. Упрощенный запуск приложения

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

Чтобы решить эту проблему, feature-u предоставляет функцию launchApp() (см .: Запуск вашего приложения).

Вот основная линия закусочной ...

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

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

Основная линия просто накапливает Аспекты и Функции и запускает приложение, вызывая launchApp().

Вот несколько важных моментов (сравните числа с *n* в приведенном выше коде):

  1. (*1*) предоставленные Аспекты (извлеченные из отдельных пакетов NPM) отражают структуру нашего стека времени выполнения (в нашем примере redux, redux-logic и feature-router) и расширяют допустимые свойства Feature - Feature.reducer, Feature.logic и Feature.route соответственно. (См. Расширяемые аспекты).
  2. (*2*)все функции приложения собраны из нашего feature/ каталога
  3. (*3*)в качестве предварительного просмотра Совместной работы функций экспортированное возвращаемое значение launchApp() представляет собой App объект, который продвигает накопленный общедоступный API всех функций.

2. Платформы React

В приведенном выше примере (см. *4*) вы видите, что launchApp() использует обработчик обратного вызова registerRootAppElm() для регистрации предоставленного rootAppElm на конкретной используемой платформе React. Поскольку эта регистрация выполняется с помощью кода, специфичного для приложения, feature-u может работать на любой из платформ React (см. Регистрация в React).

Вот несколько registerRootAppElm() вариаций:

Реагировать в сети:

React-native:

"Экспо":

3. Объект функции

Каждая функция расположена в своем собственном каталоге и продвигает содержимое аспекта через объект Feature (используя createFeature()).

Вот пример из функции устройство eatery-nod.

Как видите, объект Feature - это просто контейнер, содержащий аспектное содержимое, представляющее интерес для feature-u. Единственная цель объекта Feature - передать информацию об этом аспекте launchApp().

Мы заполним детали чуть позже, а пока обратите внимание, что эта функция передает редукторы, логические модули, маршруты и выполняет некоторый тип инициализации (_36 _ / _ 37_). Он также продвигает publicFace, который может использоваться другими функциями (например, общедоступным API функции).

Для получения дополнительной информации, пожалуйста, обратитесь к Особенности и аспекты содержания.

4. Инициализация функции

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

Это может быть любое количество вещей, например:

  • инициализировать некоторый сервисный API
  • внедрить компонент реакции утилиты в корень приложения
  • отправить действие, которое запускает процесс запуска
  • И более...

Чтобы решить эту проблему, feature-u представляет две ловушки жизненного цикла приложения, внедренные через следующие аспекты функции:

  1. Feature.appWillStart({app, curRootAppElm}): rootAppElm || falsy Вызывается один раз перед запуском приложения. Это может выполнять любой тип инициализации, включая добавление корневого элемента верхнего уровня приложения (например, экземпляра React component).
  2. Feature.appDidStart({app, appState, dispatch}): void
    Вызывается один раз сразу после запуска приложения. Типичное использование этого хука - отправка некоторого типа bootstrap action.

Вот несколько примеров из закусочной:

Инициализация FireBase:

Действие начальной загрузки:

Внедрить DOM Root Elm:

5. Совместная работа над функциями

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

Чтобы решить эту проблему, feature-u продвигает кросс-функциональную коммуникацию. Это достигается с помощью свойства Feature.publicFace Встроенный аспект. Функция может раскрыть все, что сочтет необходимым, через publicFace. На этот ресурс нет реальных ограничений. Это действительно открыто.

Обычно это включает продвижение избранных:

  • действия
  • селекторы
  • API
  • И так далее

publicFace всех функций накапливаются и отображаются через объект App (исходящий из launchApp()).

Он содержит следующие именованные узлы функций:

App.{featureName}.{publicFace}

Вот пример функции auth в eatery-nod.

Из всех элементов функции auth общедоступными являются только два действия и один селектор.

Вот как будет выглядеть объект App в этом примере:

app: {
  auth: {
    actions: {
      userProfileChanged(userProfile),
      signOut(),
    },
    sel: {
      getUserPool(appState),
    },
  },
  currentView: {   // other features
    ... snip snip
  },
}

В результате доступ к общедоступному API функции auth можно получить следующим образом:

app.auth.actions.userProfileChanged(userProfile)
app.auth.actions.signOut()
app.auth.sel.getUserPool(appState)

6. Интеграция с фреймворком

Скорее всего, ваше приложение использует одну или несколько структур (например, redux или redux-logic). Как ресурсы, необходимые этим фреймворкам, накапливаются и настраиваются для многих функций вашего приложения?

Чтобы решить эту проблему, функция-u вводит Расширяемые аспекты. feature-u является расширяемым. Он обеспечивает точки интеграции между вашими функциями и выбранными вами фреймворками.

Расширяемые аспекты упаковываются отдельно от feature-u, чтобы не вводить нежелательные зависимости (потому что не все используют одни и те же фреймворки). Вы выбираете и выбираете их на основе фреймворков, используемых в вашем проекте (в соответствии со стеком времени выполнения вашего проекта). Они создаются с помощью расширяемого API-интерфейса feature-u с использованием createAspect (). Вы можете определить свой собственный Аспект, если тот, который вам нужен, еще не существует.

Давайте посмотрим на пример сокращения от eatery-nod.

Функция device поддерживает свой собственный фрагмент дерева состояний.

Он продвигает свой редуктор через аспект Feature.reducer:

Поскольку Feature.reducer является расширенным аспектом (вместо встроенного аспекта), он доступен только потому, что мы зарегистрировали feature-redux reducerAspect на launchApp() (см. Упрощенный запуск приложения выше).

Ключевым моментом является понимание того, что feature-u (через расширение feature-redux) автоматически настраивает redux, собирая все редукторы функций в одно общее состояние appState.

Вот код редуктора…

Редуктор на основе функций - это просто обычный редуктор, который управляет частью функции всего appState. Единственное отличие состоит в том, что он должен быть украшен символом slicedReducer(), который содержит инструкции о том, где его вставить в общее состояние appState верхнего уровня.

В результате редуктор device поддерживает только состояние, относящееся к функции device (например, его маленький кусочек мира) - статус, индикатор fontsLoaded и местоположение устройства.

Боковая панель: мы используем reducerHash() функцию утилиты astx-redux-util для лаконичной реализации редуктора этой функции (предоставляя альтернативу обычному оператору switch). Я обнаружил, что при использовании такой утилиты в большинстве случаев возможно реализовать все редукторы функции в одном файле (отчасти из-за меньшей границы функции). astx-redux-util также продвигает другие Редукторы высшего порядка. Вы можете это проверить.

7. Включение функций

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

Чтобы решить эту проблему, функция-u представляет Включение функций. Используя Feature.enabled Встроенный аспект (логическое свойство), вы можете включить или отключить свою функцию.

Вот пример из функции песочницы eatery-nod:

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

Обычно этот индикатор основан на динамическом выражении, но в этом случае он просто жестко запрограммирован (устанавливается разработчиком).

Боковая панель: когда другие функции взаимодействуют с функцией, которую можно отключить, вы можете использовать объект App, чтобы определить, присутствует ли функция или нет (см. Включение функции для получения дополнительной информации).

8. Управляемое расширение кода

Как правило, доступ к импортированным ресурсам во время расширения кода в строке может быть проблематичным из-за порядка, в котором эти ресурсы расширяются. Объект feature-u App является настолько важным ресурсом (поскольку он продвигает общедоступный API всех функций), он должен быть доступен даже во время расширения кода. Другими словами, мы не можем полагаться на то, что «импортированное приложение» будет разрешено во время расширения кода.

Чтобы решить эту проблему, функция-u представляет Управляемое расширение кода.

Когда определения содержания аспектов требуют объект App во время расширения кода, вы просто оборачиваете определение в функцию managedExpansion(). Другими словами, содержимое вашего аспекта может быть либо фактическим содержимым (например, редуктором), либо функцией, возвращающей содержимое.

Когда это будет сделано, feature-u будет расширять его, вызывая его управляемым способом, передавая полностью разрешенный объект App в качестве параметра.

Вот логический модуль из функции auth в eatery-nod:

Вы можете видеть, что функция аутентификации использует действие функции устройство, требующее доступа к объекту app (см. *2*). Поскольку объект app необходим во время расширения кода, мы используем функцию managedExpansion() (см. *1*), позволяя функции-u расширять его контролируемым образом, передавая полностью разрешенный объект app в качестве параметра.

9. Продвижение компонентов пользовательского интерфейса

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

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

Функциональные маршруты основаны на очень простой концепции: разрешить состоянию приложения управлять маршрутами! Он работает через серию функций маршрутизации на основе функций, которые основываются на appState и либо возвращают визуализированный компонент, либо или ноль, чтобы позволить нисходящим маршрутам такую ​​же возможность.

Вот простой пример из функции устройство.

Этот маршрут анализирует текущее состояние приложения и отображает экран SplashScreen, пока система не будет готова:

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

Вместо этого анализируется appState, и если устройство НЕ готово, никакие другие экраны не имеют возможности даже визуализировать ... Easy Peasy!

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

10. Единый источник истины

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

Каковы некоторые передовые практики для единого источника истины в отношении функций и чем может помочь функция-u?

В разделе Лучшие практики выделен ряд интересных элементов, основанных на единственном источнике истины. Это рекомендации, потому что вы должны реализовать их в своем коде приложения (функция-u не управляет этим).

Вот пример из функции закусочные:

featureName используется для указания местоположения состояния верхнего уровня этой функции (см. *1*). feature-u гарантирует, что имя функции уникально. В результате его можно использовать для определения идентичности нескольких аспектов функции.

Например:

  • префиксные типы действий с featureName, гарантируя их уникальность для всего приложения (см .: документы feature-redux)
  • префиксные имена модулей логики с featureName, определяющие, где находится этот модуль (см .: документы feature-redux-logic)
  • в зависимости от контекста, featureName может использоваться в качестве корня формы вашего состояния функции (см. документы feature-redux)

Поскольку feature-u полагается на slicedReducer() (в пакете feature-redux), лучше всего использовать приукрашенный селектор редуктора для определения корня состояния вашей функции во всех определениях селектора. В результате определение среза сохраняется в одном месте (см. *2*).

Feature-u Преимущества

Таким образом, преимущества использования feature-u включают:

  • Инкапсуляция функций - изоляция реализаций функций улучшает управляемость кода.
  • Межфункциональная коммуникация - общедоступный API функции продвигается с помощью четко определенного стандарта.
  • Включение функций - включение / отключение функций с помощью переключателя времени выполнения.
  • Перехватчики жизненного цикла приложения - функции могут инициализироваться сами по себе, не полагаясь на внешние процессы.
  • Единый источник истины - обеспечивается несколькими способами в рамках реализации функции.
  • Интеграция с фреймворком - настройте фреймворк по вашему выбору (в соответствии со стеком времени выполнения) с помощью расширяемого API-интерфейса feature-u.
  • Продвижение компонентов пользовательского интерфейса - через маршруты функций
  • Сведите к минимуму проблемы зависимости порядка функций - даже в коде, который раскрывается в строке
  • Plug-and-Play - функции можно легко добавлять / удалять.
  • Simplified Mainline launcApp() запускает приложение, настраивая используемые платформы, и все это осуществляется с помощью простого набора функций.
  • Работает на любой платформе React (включая React Web, React Native и Expo)

Надеюсь, эта статья даст вам представление о том, как feature-u может улучшить ваш проект. Пожалуйста, обратитесь к полной документации для получения более подробной информации.

feature-u позволяет сосредоточить ваше внимание на «бизнес-стороне» ваших функций! Идите и вычислите !!

"Использованная литература"