ДжазТим — надежный технологический партнер

Agile разработка ПО на Java

Знакомство с Mobx 5

О статье

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

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

Что такое MobX?

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

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

В библиотеке MobX хорошо читаемый и лаконичный синтаксис, позволяет использовать привычные javascript классы спецификации ES6 для реализации хранилища.

В дополнение: MobX содержит встроенные инструменты отладки для отслеживания поведения рендеринга и зависимостей модели вашего приложения.

Как начать использовать MobX

Установка MobX

Для установки MobX нужно прописать одну из следующих команд:

  1. Через Yarn — yarn add mobx.
  2. Через NPM — npm install —save mobx.

После этого пакет MobX добавится в package.json и его компоненты можно будет использовать в проекте с помощью импорта:
import {observable, action, computed, makeObservable} from “mobx”.

Основные концепции

Перейдём непосредственно к описанию некоторых основных возможностей и функционала MobX, с которыми мы будем работать.

Наблюдаемое состояние (Observable state)

Свойства, целые объекты, массивы, Maps и Sets можно сделать наблюдаемыми. Для того, чтобы сделать объект наблюдаемым, нужно указать аннотацию для каждого свойства, c использованием makeObservable или makeAutoObservable.

makeObservable(target, annotations?, options?)

makeObservable используется для перехвата существующих свойств объекта и делает их наблюдаемыми. В target можно передать любой объект JavaScript. Обычно, makeObservable прописывается в конструкторе класса и первым его аргументом является this. Annotations сопоставляет аннотации с каждым элементом. При указании декораторов (observable, action…) символ @ следует опустить. Пример:

Пример указания декораторов (observable, action…)

makeAutoObservable(target, overrides?, options?)

Отличие makeAutoObservable от makeObservable — это то, что makeAutoObservable выводит свойства по умолчанию. Overrides используется для переопределения поведения с использованием аннотаций. Также мы можем указать false, чтобы исключить метод или свойство из обработки. makeAutoObservable может быть более компактной и простой в использовании, чем makeObservable, т.к. новые члены не указываются явно. Но, makeAutoObservable нельзя использовать в классах, которые имеют ключевое слово super или являются подклассами.

@Observable

C помощью аннотации @observable MobX устанавливает наблюдение за существующими структурами данных. Определяет отслеживаемое поле, в котором хранится состояние. Данные могут быть определены как объекты, массивы, а также как экземпляры классов. Состояние — это данные, которые управляют приложением. За этим состоянием нужно постоянно наблюдать. Его можно менять, и в случае, если состояние меняется, нужно реагировать на эти изменения.

import { observable } from ‘mobx’;

class User {
  id = Math.random();
@observable name = ‘ ‘;
@observable age = ‘ ‘;
@observable hungry = false;
}

Заметка: аннотации в javascript — это ещё не стандартизированная технология будущей спецификации ES.Next(ECMAScript), и чтобы они работали в вашем приложении, должны использоваться транспайлеры в ES6+ (Babel). Но есть и альтернатива этому, которая описана в документации Mobx.

Вычисляемые значения (Computed values)

С помощью @computed аннотаций вы можете определить данные, которые будут автоматически обновлены при изменении других данных. Но это выполняется в том случае, если значения наблюдаемые. Если же значения ничем не наблюдаемые, то они приостанавливаются целиком.

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

Вычисляемые значения можно создавать, аннотируя геттеры JavaScript при помощи аннотации @computed.

import { observable, computed } from ‘mobx’;

class UserList {
@observable users = [];
@computed get hungryUsersCount() {
        return this.users.filter(todo => !todo.hungry).length;
}
}

В этом примере кода свойство hungryUsersCount будет обновлено, если наблюдаемый массив users будет изменён.

Заметка: Если вам незнакомы функции get/set, которые присутствуют в примере, можете ознакомиться с ними по ссылке.

Реакции (Reactions)

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

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

В MobX есть несколько функций реакций, некоторые из них это autorun() и when().

Функция autorun является самой простой для понимания реакцией, и в примере ниже с её помощью мы реализовали простой вывод выражения в консоль, если данные изменились.

import { autorun } from ‘mobx’;

autorun(() => {
   console.log(user.name || user.age)
});

Также очень интересной реакцией является функция when, которая позволяет дополнительно, перед выполнением определённой логики, осуществить проверку значения данных.

class User {
   constructor () {
when(
() => this.hungry,
() => this.eatSandwich()
)
}
eatSandwich() {
       // sandwich eating logic
}
}

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

Действия (Actions)

Действия в MobX — это функция, которая помечена декоратором @action.
Аннотацию @action следует использовать только для функций, предназначенных для изменения состояния. Функции, которые извлекают информацию (выполняют поиск или фильтруют данные), не должны быть помечены как action, чтобы позволить MobX отслеживать их вызовы. Именно внутри такой функции мы можем изменять observable-данные.

import { observable, action } from ‘mobx’;

class User {
@observable name = ‘ ‘;

@action setUserName(userName) {
        this.name = userName;
}
}

Пример кода демонстрирует метод setUserName, который и является действием. Метод устанавливает имя пользователя, после чего MobX позаботится об остальном и уведомит всех слушателей, которые зависят от этого поля.

Действия не обязательно должны быть методами класса, они могут располагаться где угодно в приложении, главное, чтобы были определены вместе с аннотацией @action.

Хорошей практикой является в действиях осуществлять асинхронные ajax вызовы.

Дополнительно

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

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

Пример использования action с makeObservable:

Пример использования action с makeObservable

Как это работает?

Ознакомившись с основными концепциями MobX, стоит подытожить и рассказать вкратце, как они взаимодействуют между собой:

  1. У нас есть состояние, класс, который содержит @observable данные в виде объектов, массивов, примитивов, которые формируют модель нашего приложения.
  2. Далее, у нас есть действия Actions, которые меняют состояние и вызываются из реактивных React компонентов приложения.
  3. После выполнения действия Actions MobX проследит, чтобы все изменения в состоянии приложения, вызванные действиями, автоматически обработались всеми вычисляемыми значениями (@computed) и реакциями(autorun(), when()).
  4. Также MobX запускает автоматические реакции, которые выполнят работу, связанную с вводом/выводом, проверят и убедятся, что интерфейс нашего приложения полностью обновился.

Рекомендации, Best practices

  • Изменяйте @observable данные только в классе хранилища.
    Для изменения данных всегда реализуйте специальный метод действие в вашем хранилище и вызывайте его в компоненте.
  • Используйте вычисляемые значения.
    Вычисляемые значения, создаваемые с помощью @computed аннотации, хорошо оптимизированы, кэшируют значения данных, от которых зависят. Разработчики MobX призывают не бояться их использовать. Выделение небольших вычислений в @computed функции позволит их легко переиспользовать в других компонентах и делают код более читаемым и удобным.
  • Используйте аннотацию @observer для всех своих компонентов, которые отображают @observable данные.
    Аннотация @observer делает ваши компоненты реактивными, чтобы изменения @observable данных, которые отображает компонент, запускали процесс перерисовки компонента. Разработчики MobX рекомендуют делать больше @observer компонентов, это делает прорисовку более эффективной.
  • Не копируйте @observable данные в локальные переменные.
    Если вы сохраните @observable данные в локальную переменную компонента, то изменение этой переменной не будет отслеживаться. Если вы хотите упростить код, разработчики MobX рекомендуют определить в компоненте @computed функцию, которая будет возвращать значение @observable данных.
  • Всегда удаляйте реакции.
    Функции реакций autorun, observe, intercept возвращают функцию удаления, при вызове которой данная функция реакции прекратит работу. Это рекомендуется делать, когда функция реакции вам больше не нужна. Это позволит вовремя освобождать память приложения.
  • Не реализуйте функции асинхронных вызовов в классе хранилища.
    Отделите функции асинхронных вызовов в отдельный класс и передавайте его в хранилище приложения. Таким образом вам будет легче протестировать приложение и получить более лаконичный и читаемый код.
  • Реализуйте бизнес-логику приложения в классах хранилища.
    Такой подход улучшит организацию кода и даст возможность повторно использовать необходимые методы в других компонентах.

Преимущества и недостатки MobX по сравнению с Redux

Преимущества

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

Недостатки

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

Вывод

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

Полезные ссылки

MobX — официальная документация MobX.
Awesome MobX — это официальная коллекция самых популярных и нужных вещей, касающихся MobX. Тут вы найдёте полезные статьи и видеоучебники для изучения Mobx, примеры проектов, использующих MobX, шаблоны начальной структуры фронтенд-проекта для быстрого старта разработки.
Awesome-MobX — это неофициальная коллекция нужных вещей для MobX.
MobX-react-boilerplate — простое минимальное приложение, использующее MobX с React.
MobX-react-todomvc — пример чуть более сложного приложения, Todo-лист с использованием MobX.