company_banner

Почему мы выбрали MobX, а не Redux, и как его использовать эффективнее

Choices and consequences .. BY  Ash-3xpired. Источник https://www.deviantart.com/ash-3xpired/art/Choices-and-consequences-198140687
Choices and consequences .. BY Ash-3xpired. Источник https://www.deviantart.com/ash-3xpired/art/Choices-and-consequences-198140687

Меня зовут Назим Гафаров, я разработчик интерфейсов в Mail.ru Cloud Solutions. На дворе 2020 год, а мы продолжаем обсуждать «нововведения» ES6-синтаксиса и преимущества MobX над Redux. Существует много причин использовать Redux в своем проекте, но так как я не знаю ни одной, расскажу о том, почему мы выбрали MobX.

Как мы пришли к использованию MobX

Mail.ru Cloud Solutions — это платформа облачных сервисов. С точки зрения разработчика, у нас типичная React-админка, которая позволяет полностью управлять облачной средой: создавать виртуальные машины, базы данных и кластеры Kubernetes. Можно скачивать отчеты по балансу, смотреть графики по нагрузке и тому подобное. 

Текущий стек — TypeScript, React, Redux, Formik — нас полностью устраивал, за исключением Redux. В какой-то момент к нам пришли с задачей разработать новый проект — админку для платформы интернета вещей. Так как это был новый проект — на отдельном домене и со своим дизайном, мы решили посмотреть в сторону MobX.

Почему не Redux

Многословность

Наверное, всем надоели завывания про многословность Redux, но это реальная проблема. Посмотрите код обычного счетчика. Итак, чтобы изменить состояние нам нужны экшены:

export function increment() {
  return {
    type: 'INCREMENT'
  }
}

export function decrement() {
  return {
    type: 'DECREMENT'
  }
}

Программисты, которым этого мало, создают actionTypes:

export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'

Дальше пишем редьюсеры:

export default (state = { count: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {
        count: state.count + 1
      }
    case 'DECREMENT':
      return {
        count: state.count - 1
      }
    default:
      return state
  }
}

Мапим State и Dispatch — непонятно зачем, но почему бы и нет:

const mapStateToProps = (state) => {
  return {
    count: state.count
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onIncrement: () => {
      dispatch(increment())
    },
    onDecrement: () => {
      dispatch(decrement())
    }
  }
}

Дальше нам осталось всего лишь законнектить компонент:

import { connect } from 'react-redux'

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter)

А, нет, еще не все. Еще нужно проинициализировать Store:

import { createStore } from 'redux'
import reducers from './reducer'

const store = createStore(reducers)

export default store

И прокинуть наш Store дальше в приложение через Provider:

import store from './store'
import Page from './Page'

const App = () => (
  <Provider store={store}>
    <Page />
  </Provider>
)

Теперь тот же самый пример, но на MobX. Верстка:

import { observer } from "mobx-react"
import CounterStore from "./Counter"

const App = observer(props => {
  const { count, increase, decrease } = CounterStore

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={increase}>increment</button>
      <button onClick={decrease}>decrement</button>
    </div>
  )
})

Store:

import { observable, action } from "mobx"

class Counter {
  @observable count = 0

  @action decrease = () => {
    this.count = this.count - 1
  }

  @action increase = () => {
    this.count = this.count + 1
  }
}

const CounterStore = new Counter()
export default CounterStore

Я могу понять многословность в обмен на какую-то пользу. Например, статические гарантии в обмен на многословность типизации. Но многословность ради многословности... Зачем? Для чего?

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

  • underscopeio/reduxable

  • jkeam/reduxsauce

  • jamesplease/zero-boilerplate-redux

  • redux-zero/redux-zero

  • MynockSpit/no-boilerplate-redux

У нас тоже была подобная обертка, она называется Redux-helper. Я думаю, что каждый уважающий себя фронтендер должен написать обертку над Redux для борьбы с бойлерплейтом. Проблема в том, что если вы пытаетесь так улучшить Redux, то у вас случайно получается MobX.

Проблема выбора

Пример со счетчиком был полностью синхронным. Давайте напишем асинхронный код на MobX:

class PostsStore {
  @observable isLoading = false
  @observable posts = []

  @action getPosts = async () => {
    this.isLoading = true
    this.posts = await api.getPosts()
    this.isLoading = false
  }
}

Верстка:

import PostStore from 'PostStore'

const PostsPage = observer(() => {
  const { posts, isLoading } = PostStore

  if (isLoading) {
    return <div>Загружаем список постов</div>
  }

  return (
    <ul>
      { posts.map(post => <li>{post.title}</li>) }
    </ul>
  )
})

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

Отсутствие асинхронности в Redux — на самом деле странное решение. В моем детстве тебя могли избить и за меньшую оплошность.

Для решения внезапно возникшей проблемы асинхронности (никто не ожидал ее в вебе), сообщество создало redux-thunk, redux-saga, redux-observable и redux-loop. 

Таким образом, любую проблему Redux можно решить с помощью другой проблемы:

  • Нужна асинхронность — добавь redux-thunk.

  • Хочешь меньше бойлерплейта — возьми redux-zero.

  • Reselect для мемоизации селекторов.

  • Normalizr для хранения данных в нормализованном виде.

  • Еще нужен Immutable.js, чтобы постоянно не писать спреды.

  • Ну и хотелось бы писать в мутабельном стиле, поэтому добавим immer (от автора MobX!)

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

В итоге каждый проект на Redux — множество чужих спорных решений с модными на тот момент библиотеками. Получается «Франкенштейн», поддерживать который приходится вам, а не тому, кто его создал.

Скорость разработки

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

Это не единичные мнения. Тысячи других разработчиков на Stateofjs.com пишут, что раздутость (Bloated) и корявость стиля (Clumsy) — одни из самых нелюбимых аспектов Redux. Раздутый код дольше писать и сложнее поддерживать.

Раздутость кода — это одна из причин, почему инопланетяне еще не вышли с нами на связь.

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

Скорость работы MobX не зависит от количества компонентов, потому что мы заранее знаем список компонентов, которые надо обновить, — вместо O(n) сложности Redux.

Тормознутость Redux вшита в его парадигму. В JavaScript очень дорогая иммутабельность, и вы создаете огромную нагрузку на сборщик мусора при копировании объектов на каждое изменение. Даже просто пройти по всем редьюсерам с помощью операции сравнения строк — это очень дорого, намного дороже, чем работа с объектами по ссылке.

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

Почему не useContext

Потому что useContext не дает производительности из коробки, вам дополнительно нужно будет обмазаться useMemo и useCallback.

Посмотрите наглядный пример от пользователя @MaZaAa — alert() выскочит только один раз, при первом рендере.

Так как MobX переопределяет shouldComponentUpdate, вам не нужно за этим следить вручную — перерендерится только то, что надо, а не всё дерево.

Ненастоящие минусы MobX

1. Декораторы еще не в стандарте, но, во-первых, можно писать без декораторов, во-вторых, можно писать на TypeScript.

2. Пятый MobX не поддерживает IE11, потому что использует ES6 Proxy, который сложно полифилить. Для нас это не было проблемой, так как мы не поддерживаем браузер семилетней давности. Но если вы его по каким-то причинам поддерживаете (хотя уже сам Microsoft перестает это делать), можно использовать MobX 4 версии (UPD: или MobX 6).

3. Redux в свое время многих подкупил своими дев-тулзами. В MobX с этим тоже нет проблем, можно использовать mobx-devtools или mobx-remotedev.

4. Для Server-side рендеринга обычно на сервере формируют стейт, сериализуют его в JSON и кладут в window.__PRELOADED_STATE__. К сожалению, MobX никак вас не ограничивает в том, что вы можете положить в стейт. Там могут быть циклические данные и другие структуры, которые не могут быть однозначно представлены в JSON.

Но в целом, если вы не храните подобные структуры в стейте, то SSR с MobX — давно решенная проблема, например, в том же Nextjs с помощью useStaticRendering.

5. Мне непонятен этот аргумент, но часто можно слышать, что в MobX слишком много магии. Если не разобраться, как устроена какая-то технология, то, конечно, она покажется магией. MobX — это просто FRP, только вам не нужно вручную подписываться на наблюдаемые объекты. MobX делает это за вас и прозрачно. Он наблюдает, к каким данным вы обращаетесь, подписывается на них и так строит объектный граф.

Создать свой аналог MobX можно за полчаса.

Настоящие минусы MobX и как с ними бороться

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

Не мутировать модель в представлении

Вернемся к нашему примеру со счетчиком:

<h1>{Store.count}</h1>

<button onClick={Store.increase}>
  increment
</button>

Мы могли бы написать его вот так, то есть мы можем мутировать состояние прямо во вьюхе:

<button onClick={() => Store.count++}>
  increment
</button>

Мы решили ни в коем случае так не писать, так как еще наши деды учили разделению ответственности и MVC.

Чтобы запретить мутировать состояние вне хранилища, можно использовать флаг enforceActions в настройках MobX. Но он будет предупреждать вас только в рантайме и создавать проблемы с Promise.

Второй вариант — помечать поля объекта как private. Но в этом случае на каждое приватное поле вам придется создать геттер.

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

Не наследовать стор от стора

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

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

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

Сервисы предоставляют данные, инкапсулируют бизнес-логику и дают возможность компонентам общаться друг с другом. Так работает Ember и Angular, но к ним мы еще вернемся.

Не внедрять модель через провайдера

Документация MobX учит подключать сторы таким образом:

@inject("CounterStore")
@observer
class App extends Component {
    render() {
        return (
          <h1>{this.props.CounterStore.count}</h1>
        )
    }
}

Мы же решили просто подключать через импорты:

import CounterStore from "./Counter"

const App = observer(() => {
  return <h1>{CounterStore.count}</h1>
})

Импортируем Store напрямую и получаем все бонусы от IDE, такие как автокомплит и статический тайпчекинг.

На Хабре есть огромный тред по этому поводу, там десятки сторонников и противников подобного подхода, не буду повторяться.

Почему не Vue/Angular/Ember

У команды был большой опыт с React, поэтому мы решили не менять фреймворк. Но в целом — связка React+MobX с наблюдаемыми и вычисляемыми полями может напомнить то, как работает Vue.js.

С ограничениями, которые мы наложили на MobX, организация данных стала похожа на Angular. Вот пример из официальной документации:

export class CartService {
  items = [];

  addToCart(product) {
    this.items.push(product);
  }

  getItems() {
    return this.items;
  }

  clearCart() {
    this.items = [];
    return this.items;
  }
}

В Ember всё то же самое, один в один:

import { A } from '@ember/array';
import Service from '@ember/service';

export default class ShoppingCartService extends Service {
  items = A([]);

  add(item) {
    this.items.pushObject(item);
  }

  remove(item) {
    this.items.removeObject(item);
  }

  empty() {
    this.items.clear();
  }
}

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

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

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

Выводы

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

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


P.S. Это текстовая версия доклада с React Moscow и Panda Meetup #39.

P.P.S. Друзья из разработки бэкенда просили передать, что они ищут разработчиков на Python/Go в команду IaaS, команду PaaS и для разработки IAM. Из интересного — разработка на open source, highload, kubernetes, распределенные системы.

Mail.ru Group
Строим Интернет

Комментарии 219

    +1
    class PostsStore {
      @observable isLoading = false
      @observable posts = []
    
      @action getPosts = async () => {
        this.isLoading = true
        this.posts = await api.getPosts()
        this.isLoading = false
      }
    }

    вообще-то этот код не очень корректный, последняя установка isLoading будет вызвана вне контекста выполнения экшена getPosts (mobx все обновления выполняет синхронно), соответсвенно, и перерендер после него не произойдёт.
    Подобное поведение отдельно разбирается в разделе документации asynchronous actions


    И, пожалуй, такое "неочевидное" поведение тоже можно записать в настоящие минусы mobx-а =)

      0
      Что простите?
      1) Перерендер произойдет. (https://codesandbox.io/s/determined-browser-zfjyq?file=/src/App.tsx)
      2) Вы не поняли походу для чего нужны action/runInAction.
      3) Не советую пристально читать то, что пишут в документации к MobX, к сожалению там имеется ересь.

      P.S. вообще от action и runInAction можно легко отказаться и включить автоматический батчинг
      import { configure } from 'mobx';
      
      setTimeout(() => {
          configure({
              reactionScheduler: (f) => {
                  setTimeout(f, 1);
              },
          });
      }, 1);
      
        +1
        Перерендер произойдет. (https://codesandbox.io/s/determined-browser-zfjyq?file=/src/App.tsx)

        хм, Ваша правда… интересно, почему..


        Вы не поняли походу для чего нужны action/runInAction.

        Возможно… я опирался на вот эти строки в документации:


        By default, it is not allowed to change the state outside of actions. This helps to clearly identify in your code base where the state updates happen.

        Вынужден поверить в это:


        Не советую пристально читать то, что пишут в документации к MobX, к сожалению там имеется ересь.

        А что ещё там есть из ереси?

          +4
          хм, Ваша правда… интересно, почему..

          Потому что цикл обработки реакций запускается при любой мутации. Назначение action строго обратное: action откладывает запуск цикла на время своего выполнения.

            +4

            а, и правда) век живи — век учись! =)

          0
          вообще от action и runInAction можно легко отказаться и включить автоматический батчинг

          И когда он будет запускаться? Каждый раз при изменении observable / computed?

            0
            Я вам кидал ссылку на codesandbox как раз с примером, там mobx уже сконфигурирован и вы можете поиграться как угодно с ним и выяснить все нюансы, например натыкать this.count++ и поставить консоль логи на рендер и убедиться что он будет срабатывать только когда все ваши синхронные изменения завершатся)
            О, я даже уже делал это codesandbox.io/s/zen-surf-g9r9t там надо смотреть в консоль и комментировать/разкоментировать конфиг mobx'a чтобы смотреть на результат.

            Основной поинт такой:

            1) При инициализации все работает штатно и все реакции синхронные, это обязательно нужно т.к. в момент инита важна синхронность, лень расписывать реальные примеры из жизни, можете просто поверить на слово) Ну либо однажды в этом убедиться лично)
            2) После того, как весь синхронный код отработает, как раз через setTimeout будет изменен шедулер реакций, который будет откладывать выполнение реакций через setTimeout, то есть у вас синхронно что-то меняется, но реакции сразу же не вызываются, они будут запланированы через setTimeout после того, как все ваши синхронные изменения закончатся. Как раз это то, что нужно web приложению, чтобы не делать лишних рендеров и реакций, а подождать пока батч синхронных изменений закончится и уже после этого вызывать реакции на эти изменения.
              0
              А мне идея с setTimeout(f, 1) (вообще-то тогда уж 0, если на то пошло) совсем даже не кажется хорошей. То, что в коде написано синхронно (некая цепочка реакций), и раньше бы могло отработать синхронно (и главное — могло бы оптимизироваться компилятором) — теперь у вас на каждый шаг по цепочке будет бахать новый таск в event loop, ну и JIT в такое не умеет тоже, к слову.

              Короче, подписывание ВСЕГО вашего кода под асинхронное выполнение реакций выглядит хорошим только тогда, когда вы реально весь код контролируете, и считаете, что да, вам так делать норм. В больших проектах под много людей такое делать — ну чёт совсем не очень.
                0
                А мне идея с setTimeout(f, 1) (вообще-то тогда уж 0, если на то пошло) совсем даже не кажется хорошей. То, что в коде написано синхронно (некая цепочка реакций), и раньше бы могло отработать синхронно (и главное — могло бы оптимизироваться компилятором) — теперь у вас на каждый шаг по цепочке будет бахать новый таск в event loop, ну и JIT в такое не умеет тоже, к слову.


                При инициализации все работает синхронно, после инициализации асинхронно(авто батчинг), в 99.9% случаев в ходе работы приложения синхронные реакции не нужны.

                Короче, подписывание ВСЕГО вашего кода под асинхронное выполнение реакций выглядит хорошим только тогда, когда вы реально весь код контролируете, и считаете, что да, вам так делать норм. В больших проектах под много людей такое делать — ну чёт совсем не очень.

                В самом большом моем проекте из последних, с командой из 10+ человек вообще проблем НОЛЬ. Только удовольствие от максимально чистого и минимального кода. И от того, что монструозный проект не тормозит (общая производительность зависит только уже от быстродействия АПИ).
                Так что религиозные предубеждения и забота о JIT компиляторе вам только вставляют палки в колеса.
                Заставить фронтенд проект тормозить по вине MobX'a ну это надо серьезно постараться. Хотя если вы используете mobx-state-tree, тогда вы можете легко заставить свой крупный проект тормозить из-за гипер излишней работы в ран тайме.
                  0
                  При инициализации все работает синхронно, после инициализации асинхронно(авто батчинг), в 99.9% случаев в ходе работы приложения синхронные реакции не нужны.

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

                  Заставить фронтенд проект тормозить по вине MobX'a ну это надо серьезно постараться.

                  Да ладно, я заставлял, и даже без особых усилий. Всего лишь отсутствие throttle в нужных местах в некоторых спамящихся мутациях (а-ля позиция скролла) способно поставить любой стейт-менеджмент на колени. Там и vanillajs даже еле справляется, если написать обработку тупенько, в лоб, и так, чтоб JIT-компайлер её не смог заоптимизировать.
                    0
                    Они редчайшие, если у вас фронт тупой, и, как и множество типичных фронтов

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

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

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

                      $mol_atom не поставит. Он автоматически троттлинг реакции до следующего фрейма.

                    0
                    А мне идея с setTimeout(f, 1) (вообще-то тогда уж 0, если на то пошло) совсем даже не кажется хорошей. То, что в коде написано синхронно (некая цепочка реакций), и раньше бы могло отработать синхронно (и главное — могло бы оптимизироваться компилятором) — теперь у вас на каждый шаг по цепочке будет бахать новый таск в event loop, ну и JIT в такое не умеет тоже, к слову.

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

                      0
                      Согласен, я тут не на то ссылаюсь — заинлайнить что-то в коде реакций компилятор один фиг едва ли сможет. А вот не создавать новую стейт-машину на асинхронный запуск — таки можно, потому что синхронные реакции просто синхронно же выполнятся до конца без лишней пыли.
                0
                P.S. вообще от action и runInAction можно легко отказаться и включить автоматический батчинг

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

                  0
                  Вы забыли добавить антидребезг. При множественных мутациях состояния у вас будет создано слишком много таймеров.

                  Ничего страшного, стэк не переполнится, каких-то фризов вы не увидите. А если у вас переполнится стэк, то с вашим приложением проблемы)
                    0

                    Разумеется, он не переполнится. Но почему это повод делать лишние действия? Добавление таймера, пусть даже и "пустого", всё-таки не самая дешевая операция, зачем делать её лишний раз?

                      –3
                      — Мы оперируем гигагерцами в процессоре, не мега, а гига.
                      — Мы оперируем гигабайтами памяти, даже на мобильных устройствах. Не мега, а гига.

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

                      Разумеется, он не переполнится. Но почему это повод делать лишние действия? Добавление таймера, пусть даже и «пустого», всё-таки не самая дешевая операция, зачем делать её лишний раз?

                      Лишние действия — в замен на более чистый код, а более чистый код в этом случае побеждает. Всё просто.
                        0
                        а более чистый код в этом случае побеждает

                        Каким образом?

                          0
                          Что значит каким?))
                              @action
                              fetch = async () => {
                                  this.isFetching = true;
                          
                                  try {
                                      const response = await getApiData();
                                      runInAction(() => {
                                          this.items = response.items;
                                          this.totalCount = response.totalCount;
                                      });
                                  } catch (e) {
                                      runInAction(() => {
                                          this.items = [];
                                          this.totalCount = 0;
                                          this.error = e.message;
                                      });
                                  } finally {
                                      runInAction(() => {
                                          this.isFetching = false;
                                          this.error = null;
                                      })
                                  }
                              }
                          

                          vs
                              fetch = async () => {
                                  this.isFetching = true;
                          
                                  try {
                                      const response = await getApiData();
                                      this.items = response.items;
                                      this.totalCount = response.totalCount;
                                  } catch (e) {
                                      this.items = [];
                                      this.totalCount = 0;
                                      this.error = e.message;
                                  } finally {
                                      this.isFetching = false;
                                      this.error = null;
                                  }
                              }
                          


                          Мы избавились от 1 action и от 3х runInAction
                            0

                            Почему не использовать flow и синтакс генераторов?

                              0
                              Потому что без них все замечательно работает и автоматически всё батчится. А если работает замечательно, то зачем писать лишний код?
                                +1
                                Потому что генераторы не дружат с TypeScript:

                                const test = yield 1; // у переменной test тип any вместо number
                                
                                  0

                                  Не то, чтобы не дружит, просто генераторы не позволяют выводить тут тип, ибо yield действительно может вернуть всё что угодно в общем случае. Поэтому надо указывать ожидаемый тип явно:


                                    0
                                    Сейчас нет, но, учитывая объем запросов, в будущем подружатся.
                                    Сейчас с TS можно спокойно подружить yield*, и если в принципе переход от yield X к yield* GENWRAP(X) не вызывает жжения в пятой точке, то можно действовать примерно вот так.
                                  +1
                                  Мы избавились от 1 action и от 3х runInAction

                                  Зато получили 8 setTimeout.

                                    –1
                                    Зато получили 8 setTimeout.

                                    Какой кошмар, от этого приложение перестало работать или стало работать медленнее?
                                      0
                                      Какой кошмар, от этого приложение перестало работать или стало работать медленнее?

                                      Оправдываете халтуру.
                                      0

                                      Откуда тут 8 таймаутов?

                                        0

                                        На каждое изменение любого observable вызывается reactionScheduler, который делает setTimeout.

                                          +3

                                          Они же автобатчатся в один вызов шедулера.

                                            0

                                            Кем? Я не вижу этого в коде.

                                                0

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

                                                  –1

                                                  Это локальная переменная авторана. Вот если будет несколько авторанов, то да, будет несколько таймаутов. В $mol_atom же будет один единственный requestAnimationFrame в любом случае.

                                                    +1

                                                    Какая разница сколько там авторанов? reactionScheduler вызывается совсем в другом месте независимо от их количества.

                                                      –2

                                                      Там во всей кодовой базе только три вхождения setTimeout два из которых не релевантны. Честно говоря мне продолжение данного спора не интересно.

                                                        0

                                                        так вызов setTimeout идёт не в кодовой базе mobx, а в одном из комментариев выше

                                      0
                                      Второй вариант — пример будущих тормозов. Каждое изменение observable свойств будет вызывать autorun. action и runInAction — откладывают вызов autorun до завершения. Автор mobx сам рекомендует все изменения выполнять в рамках action
                                    +3

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

                                      –6
                                      Минимальная частота моего процессора — 400 мегагерц. В ней он очень холодный и ест очень мало энерии. Если ваш сайт заставляет мой девайс греться и есть батарею, то возникает желание закрыть этот сайт поскорее.

                                      По кайфу писать громоздкий код и жертвовать многим в угоду того, чтобы у дяди Пети на 1mAh меньше батарейки съел сеанс работы с приложением, пожалуйста я не против.
                                      Только вот не надо всех остальных под эту гребенку загонять и думать что это реально имеет значение и оказывает влияние на деньги, которые целевая аудитория приносит бизнесу.
                                      А вот поддержка «такого вот» кода РЕАЛЬНО обходится намного намного дороже и дольше по времени, для бизнеса который тебе платит, чем забота о потреблении на 1mAh меньше. Более такого на «такой вот» код ещё и надо кого-то найти, кто согласится с ним работать.
                                      На дворе почти 2021 год, а мы не микроконтроллеры программируем с тактовой частотой 32kHz и RAM в 4kb. Вот там РЕАЛЬНО надо экономить на тактах процессора и экономии в несколько байт памяти, потому что на этом уровне это действительно заметно.
                                        +3
                                        По кайфу писать громоздкий код и жертвовать многим

                                        Этот код пишется один раз, разве нет? Он вообще может быть библиотечным, один раз понятие очереди реализовали, в библиотеку вынесли, всё. В этом вашем npm же даже лефтпад есть, почему бы что-то такое не запилить?


                                        чем забота о потреблении на 1mAh меньше.

                                        Каков масштаб проблемы-то? Сколько этих мутаций на самом деле происходит в среднем приложении?


                                        На дворе почти 2021 год, а мы не микроконтроллеры программируем с тактовой частотой 32kHz и RAM в 4kb. Вот там РЕАЛЬНО надо экономить на тактах процессора и экономии в несколько байт памяти, потому что на этом уровне это действительно заметно.

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


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

                                          +3
                                          Мне не нравится подход дельца и его аргумент про мощные машинки (человек явно переоценивает пятый айфон или нексус семилетней давности), но скорость разработки и вложение сил в решение задач в ущерб «долбанке» — хороший фокус. Хотя тут больше смахивает на экономию на спичках.
                                          Строго говоря добавление таймера — дешёвая операция, хоть она и Oмега(n) (всегда столько времени, сколько таймеров уже добавлено) на вставку и создание контекстов на чтение/исполнение, всё равно даже 1000 таймеров не сравнится с рендером всего одного (!) абзаца текста с кастомным шрифтом. Так что антидребезг был бы хорош, но точно не стоит такого напора)
                                            0
                                            В одном месте десяток лишних таймеров, в другом пару лишних циклов, где то огромный список в память засунем. А в итоге то, что должно летать еле ползает.

                                              +1

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

                                                +2
                                                Это не оптимизация, это банальное «не надо делать того, что можно не делать». Особенно с учётом того, что отговорка «код будет проще» по факту сейчас не работает — можно использовать генератор и flow, который даёт точно так же «проще» выглядящий код, но без повальных setTimeout на все реакции без исключения.

                                                Вы же, когда код пишете, не говорите «ну вот я тут заведу массив неиспользуемых значений на мегабайтик, всё равно сейчас памяти у всех гигабайты, и тут не будет бутылочного горлышка»?
                                                  0

                                                  Не очень понимаю о чём весь этот тред\срач. Мне кажется ситуация простая:


                                                  1. Имеем observable модель. Стало быть при изменении нужно notify всех subscribers.
                                                  2. Можно это сделать immediately, и тогда привет либо а) костыли вроде runInAction; б) ацкие тормоза. Либо отложенно
                                                  3. Отложенная модель прекрасна. Но её можно имплементировать по-разному.
                                                  4. Можно сделать 1 setTimeout/requestAnimationFrame/nextTick/whatever и очередь обновлений (одну на всех). ЕМНИП то deferred observable в KnockoutJS так и работают. Судя по всему так же делает и $mol.
                                                  5. Но если сделать таймауты на каждый change, то получается полная ерунда.

                                                  Мне кажется весь этот срач можно завершить просто проверив как делает MobX, как в п4 или как в п5.


                                                  Или я не прав?


                                                  P.S. В KnockoutJS они намудрили и выдали возможность сделать как угодно. Там у каждого observable может быть своя модель обновлений.

                                                    +2
                                                    Не очень понимаю о чём весь этот тред\срач.

                                                    Попробуйте прочитать его еще раз, медленнее.

                                                    Можно это сделать immediately, и тогда привет либо а) костыли вроде runInAction; б) ацкие тормоза. Либо отложенно

                                                    Простите за резкость, но это бред. Синхронное оповещение подписчиков (ваше immediately) не порождает никаких костылей (серьезно, вам не нужен runInAction, он даже и при асинхронных оповещениях далеко не всегда нужен), ни тем более тормозов (если вы конечно не будете синхронно делать такой громадный объем вычислений, который таки тормоза даст).
                                                    Речь в этом треде как раз идёт о том, что предложенный «элегантный выход» делает любые оповещения отложенными, без всякого разбора. Даже если они вообще-то исполняют только синхронный код, и могли бы без этого финта ушами выполняться синхронно. И всё для того, чтоб оповещения при выполнении асинхронного кода можно было бы записать чуток короче (но нет, потому что с применением flow тоже было бы чуток короче).
                                                      +1
                                                      Синхронное оповещение не порождает… ни тем более тормозов

                                                      WAT? В смысле не порождает? Да тот же пример выше явный пример того, что порождает. Вот возьмём связку React + MobX (либо Knockout в базовом виде).


                                                      Имеем:


                                                      this.a = 1; // rerender 1
                                                      this.b = 2; // rerender 2
                                                      this.c = 3; // rerender 3

                                                      Получается 3 рендера компонента. Вместо 1-го. Первые 2 не нужны были. Это ведь множество аллокаций и довольно тяжёлые реконсиляции. Вы же не будете утверждать, что 1 запись в event loop сопоставима со 2-мя лишними рендерами?


                                                      Или я тут что-то недопонимаю?


                                                      Простите за резкость

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


                                                      вы конечно не будете синхронно делать такой громадный объем вычислений, который таки тормоза даст

                                                      Разумеется буду. Мы же пишем UI приложение. Ну пусть не "громадный", но так или иначе куда более тяжёлый, чем 1 setImmediate + [].push.


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

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


                                                      ИЧСХ то же самое сделали авторы спецификации к Promise.

                                                        0
                                                        Да тот же пример выше явный пример того, что порождает.

                                                        Вы же понимаете, что вы сейчас пытаетесь поговорить про две разные системы? Синхронное выполнение реакций ну никак не мешает асинхронно батчить изменения перед рендером (что, собственно, в mobx-react-lite и произойдет, вот только клей mobx и react — это не одно и то же, что и сам mobx).

                                                        Разумеется буду.

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

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

                                                        А мне кажется, что спам асинхронных тасков в event loop по любому поводу — это гораздо более интересная бомба замедленного действия, которая в простых случаях не рванёт и всё будет норм — но вот зато когда рванёт, то выживших не будет вообще.
                                                          0
                                                          А мне кажется, что спам асинхронных тасков в event loop по любому поводу — это гораздо более интересная бомба замедленного действия, которая в простых случаях не рванёт и всё будет норм — но вот зато когда рванёт, то выживших не будет вообще.

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

                                                            А можно с этого момента поподробнее? Вот это уже интересно и конструктивно. Полагаю, нечто подобное, должно быть во Vue. А как это реализуется в случае MobX? И реализовано ли оно так? Если да, то зачем вообще нужен runInAction?


                                                            спам асинхронных тасков в event loop это гораздо более интересная бомба замедленного действия

                                                            Тоже интересно. Предложите такой сценарий когда это может оказаться бомбой. Ну т.е. приведёт к неожиданному провалу по производительности. Сразу уточню — я имею ввиду вариант под п4, когда изменения batch-ятся. А не когда на любой чих вешается свой таймаут.


                                                            К примеру никакой бомбы за всё время использования мною promise-ов я не заметил. Был только 1 tricky case когда асинхронная природа вычисления sha1 браузерным стандартным API поставила крест на его использовании вообще и я взял синхронную JS реализацию. Но это прямо особенный случай.

                                                            0

                                                            Имхо, вся фишка mobx как раз в том, что уведомления синхронно происходят. Если хочется по дефолту асинхронно, то лучше тогда уже использовать rxjs

                                                            +1
                                                            Не нравится, не используйте, делов то куча, кому надо тот заюзает, кому нет, тот не заюзает, нервные клетки не восстанавливаются.

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

                                                            Просто сам факт использования реакта + зоопарка = мега не эффективное использование вычислительных ресурсов, но вас это не волнует, вас волнует парочка таймаутов.

                                                            Вас не волнует когда оперируя иммутбильностью вы создаете целые копии объектов выделяя кучу памяти для их хранения и заставляете GC потеть постоянно, более того выделение памяти это вообще не бесплатно для процессора, это очень накладная операция, но это ни кого не волнует, ведь тут парочка таймаутов, которая убивает производительность.
                                                            Смешно. «Борцы» за производительность.
                                                              +1
                                                              Вас не волнует когда оперируя иммутбильностью вы создаете целые копии объектов выделяя кучу памяти для их хранения и заставляете GC потеть постоянно, более того выделение памяти это вообще не бесплатно для процессора, это очень накладная операция, но это ни кого не волнует, ведь тут парочка таймаутов, которая убивает производительность.

                                                              Никогда не думал что я плюсану хоть 1 твой комментарий, но вот тут ты прав. Складывается ощущение, что JS-народ очень избирательно смотрит за производительностью.


                                                              К примеру аргумент против "иммутабельность в JS тормозит, т.к. нет иммутабельных структур данных" обычно приводят такой: "песочница в gc очень быстрая и оптимизирована под коротко живущие объекты". Хотя какой бы быстрой она не было это всё равно прорва операций аллокации и много работы для gc. Хоть как пыжся, но это приличный объём работы


                                                              Или когда ругаются против мемоизации — спорят про то что shallow-реконсиляция не бесплатна и все эти проверки отъедают CPU. Но забывают что даже 1 лишний render среднего размера компонента это ну просто в РАЗЫ большее количество работы для CPU.


                                                              Или спорят про ++i vs i++, в то время как даже два запроса параллельно выполнить забывают.


                                                              И т.д. и т.д. Один товарищ тут на хабре даже долго и упорно втирал что не использует AJAX запросы, т.к. они медленные и вместо этого у него всё на WebSocket-ах без JSON-а. А HTML на стороне бакенда он генерирует руками написанными StringBuilder.append-ми (адский код из кошмаров).


                                                              Или этот тред. Где 1 таймаут противопоставляется лишнему рендеру и таймаут рассматривается как более тяжёлая операция (WAT?). Наверное потому что "реконсиляция быстрая" :)

                                                                0
                                                                Складывается ощущение, что JS-народ очень избирательно смотрит за производительностью.

                                                                Тут ничего не складывается, так оно и есть, это не только касаемо JS, это касаемо целой индустрии. В итоге всё это приводит к абсурдным разговорам и спорам, как комментарии в этом треде от «борцов за производительность».
                                                                +1
                                                                Вы уже несколько комментариев подряд пытаетесь поставить себя по другую сторону баррикад, хотя вам довольно давно сказали про flow, и про настолько же «чистый» код, как и в ваших примерах. Так что никакой дихотомии «или-или» тут просто нет, просто вы с помощью «хитрого приёмчика» делаете работу, которой можно было и не делать, и всё так же иметь «чистый» код.
                                                                  0
                                                                  Вы уже несколько комментариев подряд пытаетесь поставить себя по другую сторону баррикад, хотя вам довольно давно сказали про flow, и про настолько же «чистый» код, как и в ваших примерах. Так что никакой дихотомии «или-или» тут просто нет, просто вы с помощью «хитрого приёмчика» делаете работу, которой можно было и не делать, и всё так же иметь «чистый» код.

                                                                  1) Мне не нравится заворачивать функцию в другую функцию flow и использовать синтаксис генераторов. Я предпочитаю async/await.
                                                                  2) Тут дело не только в асинхронных функциях, вы вообще где либо можете менять стейт и у вас будут лишние рендеры и реакции, чтобы их не было, надо все заворачивать в action/runInAction и тут flow и генераторы не помогут.
                                                                  Вопрос зачем? Если можно этого не делать и ничем при этом жертвовать не придется, я надеюсь мы уяснили что несколько таймаутов вообще ни как не связаны с ресурсами и производительностью.
                                                                  +2
                                                                  Вас не волнует когда оперируя иммутбильностью вы создаете целые копии объектов выделяя кучу памяти для их хранения и заставляете GC потеть постоянно, более того выделение памяти это вообще не бесплатно для процессора, это очень накладная операция, но это ни кого не волнует, ведь тут парочка таймаутов, которая убивает производительность.

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


                                                                  Смешно. «Борцы» за производительность.

                                                                  Однако, практика — критерий истины, а современными сайтами, написанными с подобными идеями, пользоваться невозможно. Хабр тормозит и передаёт привет из 2007-го, гмыло тормозит и передаёт привет из 2006-го, при этом почтовик на моей локальной машине сжирает в 10 раз меньше памяти, я уж не говорю о проце, чем вкладка гмыла, а может куда больше. N+1 заставляет мобильный файрфокс плавить заднюю крышку моего мобильника (при этом аналогичные новости на аналогичном опеннете я могу читать хоть с хлебопечки), ласт.фм тоже лагает (при этом что он может делать сегодня, что не мог делать в том же 2005-м или 2006-м году, непонятно), сайт юристов, через которых оформляли мне всякие визы, вообще неюзабелен, и так далее. К сожалению, я вообще малым количеством сайтов пользуюсь.


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


                                                                  Ну и здравый смысл мне подсказывает, что есть какая-то причинно-следственная связь между отношением «да у нас тут гигабайты и гигагерцы и гигаядра» и тем, что сайты тормозят, а долбанная статическая страница с околонулевой интерактивностью жрёт почти столько же, сколько индекс проекта на плюсах на 800 килострок, и больше, чем IDE для этого проекта, и чем процесс тайпчекера для одного из самых наркоманских и тяжёлых для тайпчекинга языков:


                                                            +2

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


                                                            Жизнь, увы, не ограничивается случаями, когда есть пара горячих циклов, которые можно оптимизировать вдвое и получить, скажем, 90%-й прирост производительности.

                                                              0

                                                              Если раскидать операции браузера в виде чего-то вроде этой диаграммы, то таймауты будут на одной стороне спектра (очень дешевые), а на другой стороне будет что-то вроде repaint операции.


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


                                                              Я не говорю, что вариант с setTimeout самый идеальный (можно заменить его на microtask или вообще убрать, как предлагает JustDont). Но вот обвинять setTimeout в проблемах производительности точно не стоит.

                                                                0

                                                                Поделюсь своей трустори на эту тему..


                                                                В $mol_view для виртуализации необходимо отслеживать визуальное положение элемента в реальном времени. Единственный надёжный способ это сделать — дёргать getBoundingClinetRect в requestAnimationFrame. Когда такой элемент только один, то 60 раз в секунду дёргать всё это не накладно — на моей машине это где-то пол миллисекунды или три процента нагрузки на проц, когда открыта вкладка. Но когда отслеживаемых элементов становится десятки, то без группировки в один requestAnimationFrame нагрузка на проц становится уже такой, что кулер начинает подавать голос. А с группировкой всё норм, укладываемся в 1 миллисекунду.

                                                                  +2
                                                                  Это не таймаут, это getBoundingClinetRect нагружает проц
                                                                    0

                                                                      0

                                                                      А картинка с многими элементами есть? Там случайно forced reflow не происходит?

                                                                        0

                                                                        10 элементов:


                                                                        Нет, конечно, там же ничего не меняется.

                                                                  +1
                                                                  Таким образом, миллион созданных таймаутов не перевесят даже один лишний рендер

                                                                  Это вы погорячились. 1кк таймаутов это прямо дофига. А если под рендером подразумевать рендер virtual-dom, то тем более.


                                                                  На самом деле асинхронщина не такая быстрая. К примеру у меня была задача выполнять сотни тысяч sha1 операций над короткими строками. Для этого можно воспользоваться браузерным api crypto. Но тут засада. Он возвращает promise. Итог: использовать для этого дела C++-ый crypto оказалось значительно медленнее чем взять синхронную версию на JS с использованием asm.js. Разница была — небо и земля. Версия на WASM, впрочем сильно на фоне JS + asm.js не выделялась.


                                                                  Так что все эти наши setTimeout, setImmediate, process.nextTick, requestAnimationFrame и пр. далеко не такие быстрые операции.


                                                                  Однако соглашусь с тем, что едва ли 1 дополнительный setTimeout можно сравнивать с лишним vdom-рендером хотя бы средних размеров react компонента.

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

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

                                                                    P.S.
                                                                    Затраченное время не равно процессорное время.
                                                                    Нагрузка измеряется именно процессорным временем, а не просто временем выполнения кода.
                                                                    Не стоит об этом забывать.
                                                                      0

                                                                      Честно говоря, я не силён в переключениях контекста и прочем системном программировании, но ЕМНИП то у Promise своя очередь в event loop c очень высоким приоритетом. И я думаю что, тут вся загвоздка именно в накладных расходах на создание и обработку промисов, нежели переключение контекста на уровне ОС и CPU.


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

                                                                        –1
                                                                        Это проверить легко, while(true) { }
                                                                          0

                                                                          while(true){} повешает свой поток. Это я в курсе. Но как я могу "легко проверить" повешал ли этот код ядро CPU? Вы ведь вроде об этом выше писали.


                                                                          Разве что запустить Х worker-ов, где Х = числу ядер CPU. Согласно вашей логике выше это должно намертво повешать всю систему. Опыт показывает, что это не так.


                                                                          Или я не правильно понял ваш message?


                                                                          Суть в том, что скорее всего, event loop в lubuv и работает как раз синхронно всё то время, пока его очереди не пусты. Но тут я уже лезу далеко за пределы своих познаний.

                                                                            –1
                                                                            Не все так просто, забирать больше ресурсов и забрать целое ядро себе это разные вещи.
                                                                            Короче мораль простая, не надо строить иллюзий и летать в облаках думаю что event loop тормозной, единицы и даже десятки setTimeout'ов убивают производительность и т.д. и т.п.
                                                                            Это приводит к ложному понимаю картины мира, к говнокоду, к пустым и нелепым спорам и много к чему другому.
                                                                          0
                                                                          Насколько я понимаю, ситуация когда одно приложение могло узурпировать целое ядро процессора и не отпускать его до первого прерывания, это что-то родом из 90-х и сейчас это работает более сложным образом. Тут я думаю 0xd34df00d может подсказать.

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

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

                                                                          Этого как раз довольно просто избежать при проверке. Достаточно лишь не нагружать ничем остальную систему.

                                                                        0

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

                                                                          0
                                                                          Не то чтобы не жаль, когда речь идёт о браузерах и современных вэб приложениях на React'e и т.п., то тут уже априори ресурсы сразу же съедаются в большом кол-ве и рассуждать о том, что setTimout заимпактит ресурсы просто смешно.
                                                                          Тезис гигабайт и гигагерц о том, что давным давно уже в нашем распоряжении большие мощности, поэтому не стоит доводить коммерческую разработку до абсурда и заботится о каждом тике процессора и о каждом байте выделенной памяти.
                                                          0
                                                          Конечно, не гигагерцами и не гигабайтами оперируем.

                                                          Неплохой ноут работает с 8Гб и 1.8ГГц. На всю систему. Поэтому если одно окно Хрома сожрёт гигабайт и гигагерц — всей остальной системе придётся подвинуться и потупить. А если в фоне уже висит такое окно, то ваше с такими требованиями я могу и вообще не дождаться. Например, Фейсбуком я себя не могу заставить пользоваться, хотя по работе надо. Раз в неделю захожу, и то бесит именно скоростью.

                                                          Да, монструозные фреймворки и библиотеки тоже жрут много, но это не повод считать, что так и надо, и рассчитыввать, что гигагерцы и гигабайты полностью ваши.
                                                            0
                                                            const obj = {};
                                                            
                                                            for (let i = 0; i < 100; i++) {
                                                              obj["prop" + i] = "value" + i;
                                                            }
                                                            
                                                            let result = [];
                                                            const begin = new Date().getTime();
                                                            for (let i = 0; i < 100; i++) {
                                                              result.push(JSON.stringify(obj));
                                                            }
                                                            const end = new Date().getTime();
                                                            console.log(`took ${end - begin}ms`);
                                                            


                                                            Посмотрите на этот код и на то, сколько он выполняется по времени. Потом сравните с несколькими безобидными setTimeout'ами в качестве расплаты за более чистый код и подумайте ещё раз, действительно ли оно стоит того или нет?

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

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

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


                                                              Просто зацепило про "гигабайты и гигагерцы" — их нет, рассчитывать стоит именно на мегабайты и мегагерцы.

                                                            +2

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

                                                              +1

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

                                                        0
                                                        3) Не советую пристально читать то, что пишут в документации к MobX, к сожалению там имеется ересь.


                                                        Уже только из-за этого MobX не хочется использовать. Я хочу гуглить ответы на вопросы при разработке, а не искать в хрустальном шаре.
                                                          +3
                                                          Напоминаете гугл-програмиста)
                                                          вообще я счас работаю с redux-проектом и mobx-проектом и разница колоссальна. Не больше, не меньше. В голом Redux 10 раз думаешь, прежде чем создать новый экшн, свойство, там прям изгаляешься, в MobX пишешь стор, экшены, свойства без боли, регистрации и и СМС.
                                                          И даже сейчас смотрю на Effector и из коментов взял на вооружение $mol_atom
                                                            +3
                                                            Ну, да я много и часто пользуюсь гуглом при работе. Senior front end, стаж 10 лет.
                                                        +1
                                                        Это не «минусы mobx», это минусы асинхронности в JS. Пока у вас async «красит» код (и перейти из окрашенного кода в неокрашенный просто так без дополнительных ухищрений нельзя) — то решать вопросы можно двумя способами:
                                                        1) сделать 100% кода «окрашенным» (привет redux-saga и прочие решения на генераторах) — это прекрасно, вот только быстродействие страшно просаживает, потому что async-код очень плохо оптимизируется JIT-компилятором;
                                                        2) заставлять программиста руками что-то писать в каждом случае выхода из «окрашенного» кода. Это вот как раз эти runInAction от mobx.
                                                          0
                                                          Строго говоря ничто не обязывает оборачивать в runInAction код, просто надо знать и понимать, где это может стать проблемой.
                                                          Строго говоря никто не мешает писать вызов экшена метода после возврата асинхронной функции. Просто теряется кусок магии лаконичности, но иногда рождается магия низкосвязности
                                                            –2

                                                            3) Использовать $mol_fiber или аналоги, где не нужно ничего "окрашивать" в месте вызова.

                                                              0
                                                              Есть проекты с $mol_atom чтоб «на живую» изучить?) А то смотрю вы тут адептом выступаете)
                                                                0

                                                                Публичные я собираю тут: https://showcase.hyoo.ru/

                                                                  0
                                                                  Спасибо, полезно!)
                                                                  0

                                                                  Был бы $mol_flashplayer с таймлайном, иерархией, enterFrame я бы не глядя перешёл вместо _fiber. Даже на однопоточный вариант.
                                                                  Одна такая фича сделала бы $mol недосягаемым в принципе.

                                                                    0
                                                                    таймлайном, иерархией, enterFrame

                                                                    Что это за фичи?


                                                                    _fiber

                                                                    А это кто?

                                                              0

                                                              Хм, да и posts по идее не должен установиться (ну т.е. он конечно запишется, но PostsPage об этом не узнает)… скажите, а Вы вообще запускали этот код? он правда работает?

                                                                0

                                                                Контекст тут ни при чём, перерендер происходит всегда и при любом обновлении.


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

                                                                +2
                                                                Чтобы запретить мутировать состояние вне хранилища, можно использовать флаг enforceActions в настройках MobX. Но он будет предупреждать вас только в рантайме и создавать проблемы с Promise.

                                                                Второй вариант — помечать поля объекта как private. Но в этом случае на каждое приватное поле вам придется создать геттер.

                                                                Я бы сказал, что ОБА этих варианта следует не просто применять, а даже и насаждать в коде. Почему? Потому что они ценой некоторой большей многословности (но даже рядом не стоящей с многословностью редакса) делают явными запуск мутаций и обращения к модели. Вообще, заворачивать поля модели в геттеры, а не просто выставлять их наружу — хороший тон не только для mobx, но и вообще для кода.
                                                                  0
                                                                  Вообще, заворачивать поля модели в геттеры, а не просто выставлять их наружу — хороший тон не только для mobx, но и вообще для кода.

                                                                  Даже когда они просто используются при рендеринге?
                                                                    +1
                                                                    «Просто» — это как? Возникает из воздуха, никогда не изменяется, и «просто используется»? Так это константа, а не что-то в модели вообще.
                                                                      0
                                                                      Таки константа, да, но если константу вроде
                                                                      const OneRule = observer(() => {
                                                                      ...
                                                                      const state_text = state.states[props.rule.state].name;
                                                                      ...
                                                                      }
                                                                      завернуть в геттер react будет отслеживать изменения в
                                                                      @observable rules: Rule[];
                                                                      @observable states: State[];
                                                                      ?

                                                                  +1

                                                                  Мне кажется что redux решил проблему "многословности" с помощью redux toolkit. Очень приятно было с ним поработать в первый раз (до этого я использовал исключительно mobx).

                                                                    +2
                                                                    Если не сложно, сделайте код описанной автором функциональности на RT для наглядного сравнения простых сущностей
                                                                      0

                                                                      Я думаю что минимальный вариант будет выглядеть как-то так:


                                                                      import { useDispatch, useSelector } from 'react-redux';
                                                                      import { increaseCount, decreaseCount } from './store';
                                                                      
                                                                      const App = () => {
                                                                        const count = useSelector(state => state.counter);
                                                                      
                                                                        const dispatch = useDispatch();
                                                                        const increase = () => dispatch(increaseCount);
                                                                        const decrease = () => dispatch(decreaseCount);
                                                                      
                                                                        return (
                                                                          <div>
                                                                            <h1>{count}</h1>
                                                                            <button onClick={increase}>increment</button>
                                                                            <button onClick={decrease}>decrement</button>
                                                                          </div>
                                                                        )
                                                                      }

                                                                      Store:


                                                                      import { createSlice } from '@reduxjs/toolkit';
                                                                      
                                                                      const counterSlice = createSlice({
                                                                        name: counter,
                                                                        0,
                                                                        reducers: {
                                                                          increaseCount(state) {
                                                                            state += 1;
                                                                          },
                                                                          decreaseCount(state) {
                                                                            state -= 1;
                                                                          }
                                                                        }
                                                                      });
                                                                      
                                                                      export const { increaseCount, decreaseCount } = counterSlice.actions;
                                                                        +2

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


                                                                          reducers: {
                                                                            increaseCount(state) {
                                                                              state += 1;
                                                                            },
                                                                            decreaseCount(state) {
                                                                              state -= 1;
                                                                            }
                                                                          }

                                                                        Что мы имеем:


                                                                        • reducer-ы и action-ы завязаны друг на друга 1 к 1
                                                                        • нет отдельных action-type-ов

                                                                        Redux-фанат из прошлого пришёл бы от этого в ярость. Где же reducer-ы с O(n) и waterfall? Где же PubSub говно-архитектура? Где же возможность вызывать один case-reducer-а разными action-ами? Где же столь любый перечень доступных констант action-ов. Ну и прочий бесполезный мусор, который считался жизненно необходимым любому крупному приложению :-)


                                                                        const count = useSelector(state => state.counter);

                                                                        А как же умные и глупые компоненты? А где же селекторы и всякие линзы? Вы же в store лезете руками прямо в… Кровоизлияние в мозг. Или… Хм... Оказывается можно и без двадцати уровней абстракций поверх абстракций? Redux фанаты прошлого уже зовут санитаров.


                                                                        const increase = () => dispatch(increaseCount);
                                                                        const decrease = () => dispatch(decreaseCount);

                                                                        Тут честно говоря грязновато получилось. Вполне могли бы сделать так:


                                                                        <button onClick={dispatch.factory(increaseCount)}/>

                                                                        со встроенной мемоизацией. Или что-нибудь вроде этого:


                                                                        const { increaseCount } = useReduxStore().actions;

                                                                        Было бы что-то вроде Vuex.


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

                                                                          0
                                                                          reducer-ы и action-ы завязаны друг на друга 1 к 1

                                                                          На самом деле это всё-таки дефолтное поведение. Разработчики редакса пришли к выводу, что реакция в разных reducer'ах на один и тот же action нужна обычно для 3.5 мест и далеко не в каждом проекте и запихнули его в extraReducers.
                                                                          нет отдельных action-type-ов

                                                                          Они генерятся на основе имени action'ов и слайсов и пихаются поле name соответствующего actionCreator'a. Иначе бы у людей саги не работали.
                                                                            0
                                                                            нужна обычно для 3.5 мест

                                                                            Что многим было очевидно с самого начала :)


                                                                            Они генерятся на основе имени

                                                                            Ага, я понимаю. Каждый кто писал свои велосипеды поверх redux так или иначе решали эту проблему. Я в одной из попыток даже плагин для babel-я делал. Хотя самое простое решение как раз выше — слить воедино reducer и его action.

                                                                    0
                                                                    Я думаю, что каждый уважающий себя фронтендер должен написать обертку над Redux для борьбы с бойлерплейтом.
                                                                    И я тоже написал решение для борьбы с бойлерплейтом. С помощью кодогенерации с функции вида smth(prev: MyChildState, { params }): MyChildState {} генерируется класс smthAction, у которого поля совпадают с params, редюсер (добавляется case в switch), и диспетчер, чтобы не создавать action, а вызвать метод с теми же параметрами.
                                                                    Также решена и проблема O(n), теперь это O(1).

                                                                    Но работает только с typescript.
                                                                      +2
                                                                      Ну и хотелось бы писать в мутабельном стиле, поэтому
                                                                      а мне хочется в иммутабельном писать. Пописав какое то время чистый код, приходишь в проект где «обычный порошок» и рвешь волосы на голове, когда натыкаешься на всякое такое, например почему if не сработал, хотя объекте нужное поле есть, очевидно поля не было в момент прохождения кода, кто то его мутировал позже, а консоль делает evaluation, когда мышкой тыкаешь, а не когда log вызвался.
                                                                      К хорошему быстро привыкаешь, и именно переход обратно к плохому сильно заметен.
                                                                        0
                                                                        хотя объекте нужное поле есть, очевидно поля не было в момент прохождения кода, кто то его мутировал позже, а консоль делает evaluation, когда мышкой тыкаешь, а не когда log вызвался

                                                                        При чём тут мутабильность если у вас такой «хороший» код?))) Проблема легко решается сменой работы желательно на проект с нуля.

                                                                        К хорошему быстро привыкаешь, и именно переход обратно к плохому сильно заметен.

                                                                        Смешно. Хорошее и плохое. Причем тут «A» хорошо, а «B» плохо. Говорите как есть, что вы не умеете «B», поэтому «A» лично вам больше подходит.
                                                                          0
                                                                          Не все легко меняют работу. Зависит от города, мобильности, знаний, внешних связей и т.д. Но предположу, что то была шутка.
                                                                          Человек говорит не про то, что он не умеет, а что коллеги не смогли или сделали непривычно для него (кто-то способен держать в голове 5 элементов или даже 6, но многие не больше трёх и это, обычно, потолок), так что я понимаю автора. Хоть и люблю МобХ за его возможную лаконичность, как в TS, так и в CS.
                                                                            0
                                                                            Я не умею в Б, и я никогда не видел кого либо кто умеет в Б. Всем известный фейсбук создал это все именно потому что и они также не сумели в Б, как они сказали в презентации.
                                                                              +1
                                                                              А я видел кто умеет, таких людей просто тонны, я в том числе тоже умею, поэтому и наслаждаюсь этим, как минимум любой пользователь MobX и Vue.js это умеет, потому что там везде мутабильность и реактивность. А те, кто не умеет разумеется этим не пользуются по понятным причинам. Однажды научитесь и примкнёте к рядам тех, кто наслаждается реактивностью, тут вообще ничего сложного нет.
                                                                          +2
                                                                          Ну не знаю, основная причина использовать редукс — чтобы упростить отладку апдейтов стейта, так как редьюсеры отдельно, стейт — серилизуем и экшены тоже. Mobx — это вообще не про это.

                                                                          Асинхронность — это сайдэффекты, которые мы как раз не хотим в редьюсерах.
                                                                            –2
                                                                            Писать говнокод, чтобы потом его отлаживать, да, это точно не про MobX, он такое не приветствует)))
                                                                            А вообще в чем реальная проблема отладки? Вот прям на полном серьезе.
                                                                            Если вам не хватает пары тройки console.log и в IDE «Find Usages» / «Find All References», то боюсь такого типа проект не подлежит дальнейшей поддержки и развитию и это кандидат номер 1 на переписывание с нуля и по человечески.
                                                                              +5
                                                                              Почему-то redux больше нигде не используется, кроме как в react. Но при этом без него вполне обходятся огромные приложения. А вот в средненьких react приложениях вдруг понадобились редьюсеры и прочее. Даже в Vue, похожем на React, прекрасно обходятся без redux.
                                                                              Редьюсер — это просто чистая фукция-сеттер. Причем реализована довольно костыльно — на switch-ах с передачей фактически имени функции (action), которую нужно выполнить. Другими словами, вместо нескольких функций, все запихано в большую функцию со switch.
                                                                              Селектор — заменяется обычным геттером.

                                                                              Вот как на mobx может выглядеть аналог функционала redux-ых редьюсеров и селекторов. Как видите, сайд эффекты легко можно вынести из стора. А redux-овы dispatch, actions, action creators заменяются простым вызовом функции.
                                                                                +3
                                                                                Даже в Vue, похожем на React, прекрасно обходятся без redux

                                                                                Хехе. Хехе. Они придумали Vuex. А другие ребята придумали MobX-State-Tree. А сам Redux появился от Flux. А оный ещё от кучи решений до него. Однонаправленная шина данных не бог весть какое изобретение. Я думаю redux просто стал первым распиаренным решением в мире JS. Глобальный стор — тоже довольно очевидная штука. Тут больше вопросы возникают к Абрамову — на кой чёрт столько бойлерплейта на ровном месте? :) Но, кажется, ему просто не нравится DRY

                                                                                  0

                                                                                  MobX-State-Tree хорош! почти Vuex, но не дотягивает.


                                                                                  Если нужно несколько store которые тянут данных из других store то все резко становиться очень больно, даже reselect на фоне этого не такая боль.


                                                                                  faiwer — ты как там сам? нам тебя не хватает...

                                                                                    –1
                                                                                    MobX-State-Tree хорош! почти Vuex, но не дотягивает.

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

                                                                                    Ну и самая вишенка на торте, это то, что всё приложение падает когда вам от сервера не придет какое-то поле или придет не в том формате.
                                                                                      0

                                                                                      Пробовал его только в одном среднего размера приложении (админка для внутренних нужд компании). Данные получал с graphql так что типы были точно те что нужно. На страницы с большим количеством записей был настроен paginate.


                                                                                      В итоге самые большие тормоза были как раз на стороне сервера который graphql готовил.


                                                                                      приложение падает когда вам от сервера не придет какое-то поле или придет не в том формате

                                                                                      А что оно должно делать по вашему? Выдавать NaN в самом неожиданном месте?

                                                                                        0
                                                                                        А что оно должно делать по вашему? Выдавать NaN в самом неожиданном месте?

                                                                                        1) Пользователь может принципе не дойти до того места где используется параметр, который пришел не в том формате, но из-за MST увы у него все сломается в любом случае.
                                                                                        2) Для отображения скажем кол-ва чего-то для view слоя без разницы, скормят ему 99 или «99».
                                                                                        3) Этот параметр мог вообще не использоваться в принципе у вас в приложении, но на всякий случай был добавлен в описание, или раньше использовался, а теперь перестал.

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

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


                                                                                          Для отображения скажем кол-ва чего-то для view слоя без разницы, скормят ему 99 или «99».

                                                                                          Отлично а потом в таблице попросят вывести поле "итого" и 99 рублей + 5 рублей превратятся в 995 с прямо у офигевшего покупателя на глазах, а у некоторых творческих личностей это еще и уйдет на сервер, спишется с карты бедного пользователя...


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

                                                                                          В таком случае может стоит иногда все-таки делать рефакторинг?

                                                                                            –1
                                                                                            Хотите, используй на здоровье, но называть это хорошим я бы не стал, как ни крути, но слишком много лишнего рантайма, оверхеда и спорных проверок, да и код более многословный.
                                                                                            Главное чтобы не на моем проекте))
                                                                                            А так хоть RxJS заюзайте, если имеются садомазо наклонности присутствуют)
                                                                                          +1
                                                                                          А что оно должно делать по вашему? Выдавать NaN в самом неожиданном месте?

                                                                                          Выводить ошибку в этом неожиданном месте, но не ронять всё приложение.

                                                                                            0

                                                                                            Собственно для этого необходимо использовать try-catch который и отловит ошибки на нужном месте. Приложение падает если такой обработчик не обнаружен и это будет работать и на mobx и на react, да хоть на php или javaEE.


                                                                                            Если ошибка все-таки проскочила все обработчики ошибок — то путь лучше приложение упадет (и я в sentry буду знать где и как оно упало) чем продолжает некорректную работу с неизвестным результатом и это касается любой программы на любом языке

                                                                                              0

                                                                                              А теперь представьте, что каждый компонент — это такое мини приложение. Одно мини-приложение упало — другое продолжает работать

                                                                                                0
                                                                                                А теперь представьте, что каждый компонент — это такое мини приложение.

                                                                                                Я легко могу представить если мини приложение это — movieClip. movieClip можно остановить, запустить, навтыкать в него других movieClips. И делать c ним все то, что обычно делали с mc. Верю, что к концепции флешплеера вернутся, но на качественно другом уровне. Т.е. на уровне html, а не canvas. Но не факт. Факт, что таймлайн должен быть доступен на уровне фреймворка из коробки.

                                                                                        +1
                                                                                        Когда на стор подписаны и компоненты и другие сторы — это уже не однонаправленный поток данных.
                                                                                        Я предпочитаю придерживаться этой идеи и избегаю прямого обновления стора из другого стора. Придерживаюсь того, что обновление стора может производиться только из сайд-эффектов (или экшенов, в зависимости, от терминологии). В описании Flux говорилось подобное.
                                                                                        Хотя не, в сайд-эффектах я иногда из сторов считываю данные, а не из компонентов пробрасываю. Так что у меня тоже не однонаправленный поток данных)
                                                                                          0

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


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


                                                                                          Бизнес хочет — бизнес получит. И к сожалению с этим либо придется смириться.


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


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


                                                                                          Например если бы такой аудит вовремя бы провели у печально известных toyota и boing то это спасло бы сотни если не тысячи жизней.

                                                                                      0
                                                                                      Почему-то redux больше нигде не используется, кроме как в react.

                                                                                      NgRx для Angular — это из популярного.
                                                                                      FlutterRedux для Flutter и ReSwift для Swift из менее популярного.
                                                                                        0
                                                                                        Не знал. Соболезную Angular, Flutter и Swift разработчикам, которые столкнулись с Redux.
                                                                                      +2
                                                                                      Не вспомню спикера, но им были сказаны классные слова. Редакс отлично вписывается в те проекты, где нет времени придумывать архитектуру или слабая команда разработки. Т.е. на молодых не надо много тратить времени на обучение, как говорится смотри документацию. В остальном редакс не дает особого профита
                                                                                        0
                                                                                        Однако-же он куда распространённее среди простых заказчиков Redux на UpWork, Mobx на UpWork
                                                                                          +3
                                                                                          Распространённость в целом, не значит подходит везде). Пробовали redux, вообще не зашло. Перешли на mobx, кайфуем)
                                                                                            0
                                                                                            Не означает, но для фрилансеров важна распространенность
                                                                                          0
                                                                                          Согласен с вами. Хотя если команда прям совсем слабая и нет времени придумывать архитектуру, то кажется проще взять Angular или Ember. C ember-data вам даже не надо думать как организовать структуру API.
                                                                                        0
                                                                                        расскажу о том, почему мы выбрали MobX

                                                                                        Поздравляю, вы сделали первый шаг в сторону $mol, где философия реактивности похожа, но более продвинута, а ваш первый пример выглядел бы так:


                                                                                        $my_app $mol_view sub /
                                                                                            <= Count $mol_view sub /
                                                                                                <= count?val 0
                                                                                            <= Increase $mol_button_minor
                                                                                                click?event <=> increase?event null
                                                                                                title @ \Increment
                                                                                            <= Decrease $mol_button_minor
                                                                                                click?event <=> increase?event null
                                                                                                title @ \Decrement

                                                                                        namespace $.$$ {
                                                                                            export class $my_app extends $.$my_app {
                                                                                                increase() {
                                                                                                    this.count( this.count() + 1 )
                                                                                                }
                                                                                                decrease() {
                                                                                                    this.count( this.count() - 1 )
                                                                                                }
                                                                                            }
                                                                                        }

                                                                                        Может показаться, что кода не особо меньше, однако, в вашем коде не хватает классов для навешивания стилей, идентификаторов для привязки автотестов, привязки системы сбора статистики и локализации. Когда прикручиваешь всё это к реакту объём кода вырастает в несколько раз. А тут всё это есть уже из коробки. Более того, в $mol не нужны такие костыли:


                                                                                            @action getPosts = async () => {
                                                                                                this.isLoading = true
                                                                                                this.posts = await api.getPosts()
                                                                                                this.isLoading = false
                                                                                            }

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


                                                                                        getPosts() {
                                                                                            return this.api().getPosts()
                                                                                        }

                                                                                        В итоге каждый проект на Redux — множество чужих спорных решений с модными на тот момент библиотеками. Получается «Франкенштейн», поддерживать который приходится вам, а не тому, кто его создал.

                                                                                        Это как бы философия всего Реакта, а не отдельно Редакса.


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

                                                                                        А представьте на сколько увеличится ваш перфоманс, если решитесь и от Реакта избавиться..


                                                                                        MobX — это просто FRP

                                                                                        MobX — это всё же ОРП.


                                                                                        Единственный реальный минус MobX — он дает вам слишком много свободы

                                                                                        Давайте я вам нормальных минусов накидаю:


                                                                                        1. Не поддерживает асинхронные реакции — привет костылям, как в Редаксе.
                                                                                        2. Не поддерживает нормально SuspenseAPI. Поддержать его совсем не сложно, но автор упирается.
                                                                                        3. Не умеет реконцилировать значения, чтобы сохранялись ссылки на объекты, если они структурно не поменялись.
                                                                                        4. Относительно тяжёлый для своей функциональности.
                                                                                        5. Замороченное, постоянно меняющееся апи.

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

                                                                                        Не понял, а в чём каша-то? Если вам нужна такая логика, где один стор зависит от другого, то добавление третьего лишь внесёт дополнительную сложность. Ну и наследование тут ни при чём.


                                                                                        Импортируем Store напрямую и получаем все бонусы от IDE, такие как автокомплит и статический тайпчекинг.

                                                                                        Хех, в Реакте приходится выбирать между инверсией зависимостей и статической типизацией? А и то и другое одновременно нельзя? Как вы тестируете-то это всё без возможности замокать стор?


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

                                                                                        Redux много хайпили и многие на этот хайп повелись. Теперь во многих компаниях много этого легаси, написанного вчерашними студентами на гироскутерах. Редакс никогда ни чему не был адекватной альтернативой. И любой толковый программист это понимал и приходя в такой проект первым делом выпиливал. Ну или хотя бы пилил свою обёртку над этим цирком. Так что о какой такой победе речь?

                                                                                          +5
                                                                                          Поздравляю, вы сделали первый шаг в сторону $mol

                                                                                          Звучит как угроза...

                                                                                            0

                                                                                            Ещё пара шагов и наступит жёсткая эндорфиновая зависимость от выпуска по паре новых стартапов в месяц. Остановите это безумие, пока не поздно!

                                                                                            +2
                                                                                            Не умеет реконцилировать значения, чтобы сохранялись ссылки на объекты, если они структурно не поменялись.

                                                                                            А как это делает $mol? Рекурсивно пробегает вглубь по всем полям в поисках первого отличия? Как-то шибко дорого. Или это работает по-другому?

                                                                                              0

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

                                                                                                +1
                                                                                                будет не менее дорого

                                                                                                Всё так, кроме одного жирного НО. Это будет дороже ТОЛЬКО тогда, когда объект и правда не изменился. А что прикажете делать во всех остальных случаях?


                                                                                                На всякий случай уточню — оно у вас и правда в глубину всё сверяет, как я выше предположил, или всё не так… спорно? Я к тому, что внутри может быть что-нибудь весьма объёмное.

                                                                                                  0

                                                                                                  А часто ли дерево данных меняется всё целиком?


                                                                                                  Да, правда. Что, например, объёмное?

                                                                                                    0
                                                                                                    А часто ли дерево данных меняется всё целиком?

                                                                                                    Не понял, а зачем ему меняться целиком. Достаточно одного поля в глубине.


                                                                                                    Что, например, объёмное?

                                                                                                    Да, что угодно. Какой-нибудь большой граф данных на десяток другой мегабайт. Зависит от задачи. В случае какого-нибудь редактора школьных расписаний там могут быть тысячи значений. В случае графического или текстового редактора десятки тысяч. Я делал на KnockoutJS визивиг-редактор документов, который пережёвывал документы под 1000+ А4 листов. Короче говоря, был бы инструмент, задача найдётся.


                                                                                                    Да, правда.

                                                                                                    1. А если внутри observable его поля в глубину тоже observable — есть ли какие-нибудь оптимизации\проверки, чтобы хотя бы их не сверять в глубину без нужды?
                                                                                                    2. Это поведение как-нибудь отключается? Я не знаю, какой-нибудь флаг типа reconcilateOnChange: boolean?
                                                                                                      –1
                                                                                                      Достаточно одного поля в глубине.

                                                                                                      Ну вот и обновятся лишь ссылки на объекты по пути до этого одного поля. А все остальные не изменятся и соответственно не будет никаких зависящих от них вычислений.


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

                                                                                                      Такие графы нет смысла хранить JSON деревом. Лучше иметь реестр узлов, а в узлах связи вида "родитель", "лидер", "список детей". В не POJO объекты $mol_conform конечно по умолчанию не лезет — они сравниваются по ссылке. Но можно задать хендлер для своего класса. Соответственно, если хочется выключить это поведение — достаточно вернуть не POJO значение.

                                                                                                        0
                                                                                                        В не POJO объекты $mol_conform конечно по умолчанию не лезет — они сравниваются по ссылке

                                                                                                        В смысле по ссылке? Ссылка же останется неизменной при изменении значения. Мутабельность же. Или мы о разных вещах разговариваем. Я имел ввиду что алгоритм реконсиляции мог бы проверять, наткнувшись на, observable поле, не все его данные в глубину, а просто дёрнуть какой-нибудь флаг _changed или скажем сравнив номер версии. Ну это уже зависит от имплементации. Тогда части сравнений можно было бы избежать.


                                                                                                        Т.е. я правильно понимаю, что у вас при изменении значения оно будет записано как изменённое, только если что-то внутри фактически было изменено, и подсунув аналогичный объект как значение, ничего не произойдёт? И что в глубину проверка будет осуществлена только для POJO значений, а любые вложенные реактивные элементы в глубину проверяться не будут?

                                                                                                          0
                                                                                                          Ссылка же останется неизменной при изменении значения. Мутабельность же.

                                                                                                          Не, $mol_atom ничего не заворачивает в прокси, как MobX. Соответственно, если если измените в любом объекте нереактивное свойство — ничего не протрекается.


                                                                                                          // Зависимость от части данных
                                                                                                          @ $mol_mem
                                                                                                          bar() {
                                                                                                              return this.foo().b
                                                                                                          }
                                                                                                          
                                                                                                           // Записали данные - bar обновится и изменится
                                                                                                          this.foo({ a : 1 , b : [ 2 , { c : 3 } ] })
                                                                                                          
                                                                                                          // Обновили данные - bar обновится, но не изменится
                                                                                                          this.foo({ a : 2 , b : [ 2 , { c : 3 } ] })
                                                                                                          
                                                                                                          // Ничего не произойдёт
                                                                                                          this.foo().b.push( 4 )

                                                                                                          Т.е. я правильно понимаю

                                                                                                          Агась.

                                                                                                      0
                                                                                                      Давайте уже ссылку на codesandbox со своими $mol_atom'ами я хочу проверить это руками и глазами, а не по вашим комментариям)
                                                                                              +5
                                                                                              Без вашего комментария пост был бы неполон)
                                                                                              Даже если $mol лучше по всем параметрам, я не знаю как его продать руководству и команде. Не знаю как нанимать людей на $mol. Не знаю зачем менять реально работающее «достаточно хорошее» решение, на потенциально «идеальное».
                                                                                              Дальше синтаксиса шаблонов $mol никто в моей команде не будет смотреть. <=> — что это такое? $.$$ это мои удивленные глаза.

                                                                                              Как вы тестируете-то это всё без возможности замокать стор?

                                                                                              Jest позволяет замокать любой импорт. Более того, он может замокать любую функцию (даже не импортируемую) любого модуля. Сейчас ведь не 1994 год, в самом деле.
                                                                                                0
                                                                                                Не знаю зачем менять реально работающее «достаточно хорошее» решение, на потенциально «идеальное».

                                                                                                Вот так и говорите что вас всё устраивает, а не "не знаю как продать".


                                                                                                Дальше синтаксиса никто в моей команде не будет смотреть.

                                                                                                Ну и команду вам там набрали..


                                                                                                Jest позволяет замокать любой импорт. Более того, он может замокать любую функцию (даже не импортируемую) любого модуля.

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

                                                                                                  +2
                                                                                                  Дальше синтаксиса никто в моей команде не будет смотреть.

                                                                                                  Ну и команду вам там набрали..

                                                                                                  Ну вообще-то синтаксис имеет огромное значение, читаемость, легкость написания, восприимчивость с первого взгляда и дальнейшая поддержка крайне важны, все это отсутствует у $mol'a, так что синтаксис шаблонов $mol увы рассчитан на вас одного)
                                                                                                    0

                                                                                                    Вы, как и многие, путаете читаемость с привычностью.

                                                                                                      +3
                                                                                                      Я думаю такая привычность никому не нужна, мне в том числе. Под капотом это может быть всё что угодно, хоть самое лучшее решение на планете, но из-за этого синтаксиса увы, оно так и останется лишь для одного человека)
                                                                                                        0

                                                                                                        Вот, опять перепутали.

                                                                                                0

                                                                                                А ещё он не очень шустрый и ленивореактивный реестр объектов приходится создавать через костыли:

                                                                                                0
                                                                                                Вопрос: а вы щупали effector? Если «да», то что оттолкнуло?
                                                                                                  –1
                                                                                                  По-моему всё очевидно, effector это как бы явная и ручная подписка в виде постоянного втыкания в каждом компоненте
                                                                                                  const counter = useStore($counter)

                                                                                                  вместо того чтобы просто сразу использовать переменную counter там где нужно, без предварительной ручной подписки в каждом компоненте который будет ее использовать.
                                                                                                    0
                                                                                                    Конкретно Effector не рассматривали, но на самом деле такого добра на рынке как грязи – Effector, Reatom, Storeon, Unstated. Если честно, я не знаю как из них выбирать и как потом это продать команде.
                                                                                                    0

                                                                                                    Как по мне, многословность redux существенна только в маленьких проектах. В примере, где логики две строки — да, многословно, в реальном коде — терпимо.


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


                                                                                                    МобХ опять сводит все это в одно место.


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

                                                                                                      0
                                                                                                      Так не надо писать сайд-эффекты в сторах Mobx-а и сторах прочих стейт-менеджеров. Вынесете сайд-эффекты отдельно, как делаете в redux. У сторов должна быть задача только с данными работать.

                                                                                                      А что из себя представляют редьюсеры и чем более простым заменяются я написал в этом комменте.
                                                                                                        +1
                                                                                                        Не сводит, люди сводят используя MobX)
                                                                                                        я бы сказал ровно наоборот — в маленьких проектах эта многословность туды-сюды, а в больших каждое добавление редуктора, создание connect-та с мапингом диспатчер/стейта выглядит, как то, что кто-то с больным воображением и садистским уклоном решил сделать штуку, которую распиарили на столько, что людям даже понравилось!) (не, я понимаю что редакс всяко лучше флюкса, а то были первые нервные шаги в «тру-реактивности» на большом рынке, но выглядит именно так).
                                                                                                        Проще: любые правки в «сторе» (т.к. строго говоря стора в Redux нет) это боль. И чем больше аппка — тем больше боли т.к. как ни крути, а веб-апп это большаяя трансформация данных на событиях.
                                                                                                          +5
                                                                                                          Код значительно медленнее превращается в бардак.

                                                                                                          В случае типичного old-school Redux изначальное состояние кода уже бардак. Но вот технический долг может копиться медленнее, чем в случае observable решений. Однако, имхо, стоимость разработки получается дороже для проектов любой сложности. И чем динамичнее меняется проект, тем больше по нему бьёт Redux, т.к. он заставляет разводить в коде просто чудовищное количество бюрократии. И большая часть этой бюрократии там нужна "на всякий случай".


                                                                                                          Например на кой чёрт нужна модель Pub Sub, из-за которой все строчат свои switch-case и из-за которой главный reducer имеет O(n)? А сделано это для того, чтобы на 1 action можно было подписаться в разных частях приложения (крайне спорное решение с точки зрения как чистоты кода, так и архитектуры в целом). В проекте может лишь пару раз возникнуть такая потребность, или не возникнуть вовсе, на народ упорно строчит reducer-ы и action-ы и action-type-ы отдельно. В худшем случае в разных файлах и тогда к этому всему добавляются ещё и тонны export-ов и import-ов. В итоге даже минимальные изменения в коде превращаются в "я задел 12 файлов" и везде что-нибудь поправил. В то время как в менее бюрократичных системах это может быть 1-2 строки, которые не оторваны друг от друга.


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


                                                                                                          И если это не бардак, то что это?


                                                                                                          Самое неприятное место для меня был connect

                                                                                                          А это вообще замечательная бомба с O(n) :) Мало того, что reducer вызывается в глубину на любой чих, так ещё и mapStateToProps вызываются все и всегда. Просто замечательно масштабируемая штука… Это была ирония.


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

                                                                                                          Т.е. изначально концепция была: ай-ай-ай, не мешайте презентацию и бизнес-логику в одном компоненте. Разделяйте их на глупые и умные компоненты! А теперь — уау! хуки! к чёрту connect-ы, теперь будет сгребать всё в одну кучу и руками вызывать dispatch-и. Что случилось, то? :)

                                                                                                            0
                                                                                                            все строчат свои switch-case
                                                                                                            Вот прямо все? И никто не использует handleActions()?
                                                                                                          0
                                                                                                          Для функциональных компонентов нужно использовать useObserver. Если вы пишете код так, как писали в статье — вас ждет большой рефакторинг в скором времени.
                                                                                                          Последнее время экспериментирую с mobx-keystone и это хорошая альтернатива почти не развивающемуся mobx-state-tree
                                                                                                            +1

                                                                                                            useObserver как раз лучше не использовать, потому что его поведение контринтуитивно. Эта штука только для тех, кто совершенно точно знает что делает.

                                                                                                              0
                                                                                                              «контринтуитивно» наверное вам лучше документацию почитать.
                                                                                                              +3
                                                                                                              Приходить и говорить людям, что им прям нужно использовать API, которое в третьей версии mobx-react-lite deprecated — это отлично. Кого-то и впрямь в скором времени ждет рефакторинг.
                                                                                                                0
                                                                                                                хм и правда, как то ни предупреждений ничего при использовании не показывало… Но надо сказать, что Deprecated именно потому что не правильно использовали, а не что то не так.
                                                                                                                  0
                                                                                                                  Ну это так для большинства JS либ, мало кто хочет выдавать портянки console.warning'ами а-ля реакт или ангуляр. Но на самом деле объявление useObserver deprecated было немного ожидаемым, потому что над MobX работают люди, довольно неплохо (относительно среднего) представляющие себе, как должен выглядеть хороший public API, а к useObserver было весьма много вопросов (гляньте в issues).
                                                                                                                    0
                                                                                                                    Самое смешное что <Observer/> который предлагают использовать, по сути вызывает useObserver, шило на мыло.
                                                                                                                      0
                                                                                                                      Так в этом собственно и соль — Observer как реакт-компонент втыкается в рендер (больше его никуда не воткнуть) и используется в правильном контексте. А useObserver пытались вешать совсем не на рендер, а он таки для этого не предназначен.
                                                                                                                        0
                                                                                                                        да… чисто в ключе хуков это было странно для новичков. Но один раз понимаешь что это… и понятно.
                                                                                                                        Сейчас Observer работает как Context.Consumer, что выглядит оч не обычно, т.к. требует передавать функцию вовнутрь и useObserver как по мне выглядит сильно красивее.
                                                                                                                        0

                                                                                                                        Суть в том, что <Observer/>, в отличии от useObserver, изолирует любые рендеры внутри себя, а потому его довольно сложно использовать неправильно. Кроме того, <Observer/> можно использовать для оптимизации рендеров, в отличии от useObserver...

                                                                                                                0
                                                                                                                Что так поздно). Выступали в Иннополисе, когда снег лежал, а статью только-только выпустили)))
                                                                                                                  0
                                                                                                                  Карантин спутал все планы)
                                                                                                                  +3

                                                                                                                  На мой взгляд, Redux это не столько библиотека, сколько набор принципов, объединяющих Flux и Elm архитектуры.


                                                                                                                  Все проблемы с бойлерплейтом и выбором инструментов для redux решаются с помощью https://redux-toolkit.js.org/. Этот инструмент разработаный той же командой и рекомендуемый на первой же странице в документации https://redux.js.org/. Жаль, что официально рекомендованный инструмент появился так поздно.


                                                                                                                  Насчет "много магии в MobX" я могу объяснить. Redux очень предсказуемый, его исходники можно изучить за 30 минут, исходники MobX, для меня, выглядят сложнее. Глубокое понимание работы инструмента внутри, позволяет чувствовать себя увереннее при работе с ним.


                                                                                                                  В статье говорится, что MobX сторы что-то вроде сервисов в Angular. Однако в Angular также часто используют аналог redux https://ngrx.io/.


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


                                                                                                                  С появлением starter kit, я на данный, момент не вижу преимущества перехода с Flux на MV* и с Redux на MobX. Может быть кто-то из вас сможет меня переубедить, но эта статья не убедила :)

                                                                                                                    +4
                                                                                                                    Все проблемы с бойлерплейтом и выбором инструментов для redux решаются с помощью redux-toolkit.js.org.

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

                                                                                                                    Жаль, что официально рекомендованный инструмент появился так поздно.

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

                                                                                                                    Насчет «много магии в MobX» я могу объяснить. Redux очень предсказуемый, его исходники можно изучить за 30 минут, исходники MobX, для меня, выглядят сложнее. Глубокое понимание работы инструмента внутри, позволяет чувствовать себя увереннее при работе с ним.

                                                                                                                    Leftpad тоже очень предсказуемый. Это не означает, что ваш код станет прям сильно лучше, если вы начнете использовать leftpad. С редаксом то же самое — его внутренняя простота это плюс, но плюс крайне малозначимый на фоне полезности фич, которые он даёт.

                                                                                                                    В статье говорится, что MobX сторы что-то вроде сервисов в Angular.

                                                                                                                    Сторы MobX — это буква M в архитектуре MVC. Сервисы в ангуляре — это штука, связывающая M с другими буквами. В случае с MobX штукой, связывающую M с прочими частями — будет сам MobX.

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

                                                                                                                    «Если воспроизвести» — это вообще не аргумент. Инструменты для логгинга есть везде, что в редаксе, что в mobx. С подробными логами «легко понять причину» вообще нигде не вопрос.
                                                                                                                      +2
                                                                                                                      Вообще строго говоря mobx это про MVVM и в самом общем случае:
                                                                                                                      M — это данные (скорее всего с бэкенда) + логика (скорее всего сервисы),
                                                                                                                      VM — observable поля (состояния вью, обычно сторы)
                                                                                                                      V — понятно
                                                                                                                      Весь смысл MV* архитектур в том что букву M можно переиспользовать в дугих приложениях и в теории даже заменить react на angular, например. Другое дело, что на клиенте это скорее всего излишне и поэтому зачастую store = VM + M