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

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

Мне нравится составлять диаграммы своих мыслей, поэтому я сделал следующее:

Повторное использование кода

После нескольких дней создания прототипа я понял, что подход «напишите один раз, разверните на многих платформах» неэффективен и неэффективен и оставил меня еще менее уверенным в конечном результате моего исследования. Некоторые технологии, заслуживающие упоминания:

  • React-native
  • React-native-web
  • ReactXP

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

  • Компоненты пользовательского интерфейса.
  • Логика приложения.

С этого момента я собираюсь сосредоточиться на части своего исследования логики приложения.

Логика приложения

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

Redux

Раньше мы использовали redux, поэтому я попытался создать локальный автономный пакет, содержащий хранилище redux, действия и редукторы. Простой пример можно увидеть в приведенном ниже коде.

Import { createStore } from 'redux'
/**
 * This is a reducer, a pure function with (state, action) => state signature.
 * It describes how an action transforms the state into the next state.
 *
 * The shape of the state is up to you: it can be a primitive, an array, an object,
 * or even an Immutable.js data structure. The only important part is that you should
 * not mutate the state object, but return a new object if the state changes.
 *
 * In this example, we use a `switch` statement and strings, but you can use a helper that
 * follows a different convention (such as function maps) if it makes sense for your
 * project.
 */
function counter(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}
// Create a Redux store holding the state of your app.
// Its API is { subscribe, dispatch, getState }.
let store = createStore(counter)
// You can use subscribe() to update the UI in response to state changes.
// Normally you'd use a view binding library (e.g. React Redux) rather than subscribe() directly.
// However it can also be handy to persist the current state in the localStorage.
store.subscribe(() => console.log(store.getState()))
// The only way to mutate the internal state is to dispatch an action.
// The actions can be serialized, logged or stored and later replayed.
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1

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

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

Боль

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

Mobx-state-tree (MST)

Гант Лаборд упомянул дерево состояний mobx (MST), когда мы обсуждали управление состоянием для приложений React. Гант Лаборд упомянул, что infinitered оценивал его для следующей итерации шаблона Ignite под кодовым названием Bowser. M ST был построен на mobx, и я использовал mobx в прошлом, поэтому решил попробовать.

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

Приведенный ниже код является примером счетчика, реализованного с использованием MST.

import { types } from "mobx-state-tree"
// Create store definition
const CounterStore = types.model("CounterStore", {
    counter: types.number
}).actions(self => {
    return {
        increment() {
            self.counter ++
        },
        decrement() {
            self.counter --
        }
    }
})
// initialize the store
const counter = CounterStore.create({
    counter: 0
})
counter.increment()
console.log(counter.counter) // 1
counter.increment()
console.log(counter.counter) // 2
counter.decrement()
console.log(counter.counter) // 1

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

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

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

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

В конце концов, мы смогли создать значительную часть логики нашего приложения и протестировать ее без пользовательского интерфейса. Это упражнение изменило мое представление о состоянии приложения. Наша команда UX была занята другим проектом, и этот метод помог команде UX догнать нас. Я считаю, что этот метод также отлично подходит для создания прототипов. Представьте себе возможность подключить пакет логики приложения с помощью такого инструмента, как FramerX или Invision Studio. Возможности ………….

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