Pull to refresh

Comments 40

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

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


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

В какой момент она перестанет это делать?

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

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

Это предусмотрено: если обернуть мутирующий код в декоратор action (или transaction) — то реакции вызовутся не сразу, а при выходе из функции.

Так и было реализовано в KnockOut. Проблема только в том, что сначала на эту тему не заморачиваются, а когда кода становится много и начинает припекать, начинают везде расставлять эти костыли. Это как с экранированием — оно должно по умолчанию работать. А вот если где-то не надо, то уже по явному требованию разработчика.

Вообще-то в KnockOut изначально было реализовано обновление всего синхронно и сразу же, а отложенные обновления ввели только в версии 3.4, да и то значением по умолчанию не является. Кроме того, в KnockOut зависимые значения (pureComputed) и реакции (computed) выполняются вперемешку, в то время как mobX их строго различает.


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

В 3.4 сделали возможность сделать по умолчанию все вычисления отложенными. А до этого — приходилось в каждое узкое место вставлять троттлинг.


Чем "зависимые значения" отличаются от "реакции" и что плохого в том, что они исполняются вперемешку?


Не, я не натыкался. У меня данные целиком менялись за раз.

Троттлинг — это троттлинг. Отложенные вычисления — это более простой механизм. Они отличаются как таймеры и микротаски.


Чем "зависимые значения" отличаются от "реакции" и что плохого в том, что они исполняются вперемешку?

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

Пользователь ничего не увидит до завершения обработчика события.


Реакции получается надо просто первыми обрабатывать. Реакции лучше вообще искоренять, чтобы не было лишних пересчётов.

Да с фига ли реакции — первыми? У нас задача минимизировать побочные эффекты, а не максимизировать!


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

Ну так они же могут что-то поменять и придётся перевычислять зависимые значения.


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

Наткнулся я тут случайно на наш старый разговор… С тех пор я лучше разобрался с архитектурой MobX и могу поправить.

Слоев в MobX не два, а неограниченное количество, лишних пересчетов нигде не делается.

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

Есть ли там разделение состояний "точно не актуально, нужен пересчёт" и "возможно не актуально, нужно проверить не изменилась ли хотя бы одна зависимость"?

Да, есть. Первое называется STALE, второе — POSSIBLY_STALE.

Когда меняется базовое значение — все его производные, включая косвенные, переходят в POSSIBLY_STALE.

Когда некоторое зависимое значение пересчитывается, оно сравнивается с предыдущим. Если изменилось — то все его прямые производные оказываются в состоянии STALE.
Оно сохраняется и воспроизводится.

Они на пол пути к абстрагированию от асинхронности :-)

Knockout.js + ES6 + React(как view) = MobX

Забавно получается, React + Flux(как концепция) был предложен именно как альтернатива MVC/MVVM, чтобы покончить наконец с адом кроссзависимостей данных, которое всегда происходит при использовании паттерна observable.
Если здесь еще есть front-end разработчики с опытом больше хотя бы трех лет, они вспомнят какого это — когда observable стоит на observable и observable'ом погоняет, и над ними начинают появляться computed c некоторым throttle, чтобы хоть немного развязать во времени клубок вызывающих рендер обновлений данных.

Flux-подход был решением, путем введения unidirectional data flow которым можно полностью управлять и вызывать render через setState когда нужно. Некоторые flux-библиотеки испортили этот подход, скрывая от программиста некоторые возможности управления рендером, а react-router еще и подлил масла в огонь, конкурируя за управления стэйтом с flux.

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

MobX хорош для старта и небольших проектов. Не переоценивайте его.
Flux-подход был решением, путем введения unidirectional data flow которым можно полностью управлять и вызывать render через setState когда нужно. Некоторые flux-библиотеки испортили этот подход

Некоторые? Сама концепция React+Flux строится на том что мы на каждое изменение стора (неважно один он у вас или несколько) делаем render, потому что он «бесплатный» и у нас есть Virtual DOM, и только в исключительных случаях используем shouldComponentUpdate. Mobx (и mobx-react) как раз позволяет использовать sideways data loading, когда у вас ре-рендер происходит не на каждый чих стора, а при изменении конкретных данных, которые использует компонент
> Сама концепция React+Flux строится на том что мы на каждое изменение стора (неважно один он у вас или несколько) делаем render, потому что он «бесплатный»

Это в приложениях уровня TODO, а в реальных проектах с сотнями компонент и десятками сторов так никто не делает, иначе отладка становится невыносимой и рендер/сравнение виртуального дома становится дороже чем обновить реальный. Чтобы этого небыло, применяют техники, когда ждут выполнения всех асинхронных запросов в экшене и или тому подобное (можно думать об этом как о транзакциях) — это возможно и в redux и в relay
Я уж не знаю что там за реальные приложения где ходят сразу по несколько асинхронных запросов, но дело же не только в них. В сторах хранится state, который содержит не только данные, которые получают асинхронными запросами.
Да даже если и ждут, классика Flux — пришел ответ с сервера, у нас данные нормализованы и лежат по своим сторам. Мы подождали, пускай и несколько, асинхронных запросов и сразу 10 сторов эмитят событие 'change'. Начинаются каскадные ре-рендеры, которые надо вручную обходить через shouldComponentUpdate (см ниже)

У redux другая болезнь, в реальном, как вы говорите, приложении, у вас либо мало компонентов подписаны на единственный стор и тогда у вас боль с пробрасыванием данных через props или context, либо подписано много компонентов и тогда на каждый чих стора у вас… тадам — ре-рендеры, которые надо вручную обходить через shouldComponentUpdate =)

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

Вот чтобы избавить от этой боли и был создан mobx, и это не имеет ничего общего с ужасом типа Knockout, имхо.

Я бы формулу написал по-другому.


Knockout.js + ES6 — View = MobX

А Knockout.js + ES6 + ESX = MobX + React?

observableTodoStore.addTodo(«try MobX»);
observableTodoStore.todos[0].completed = true;

const store = observableTodoStore;
store.todos[0].completed = !store.todos[0].completed;
store.todos[1].task = «Random todo » + Math.random();
store.todos.push({ task: «Find a fine cheese», completed: true });

странный подход — то работать через апи, то прямо в кишки лезть…
а ведь если работать через апи класса, то неконсистентного состояния и быть не могло бы =)

В целом MobX привносит «магию» для замены простой концепции event dispatching (или pub/sub, если так приятнее).
Более того, если в апи добавить метод change, который будет изменять название и описание задания то, согласно коду выше, это породит 2 события изменения и изменение DOM произойдет 2 раза.
И это на примере кода уровня «hello world».
В целом да, но это так же как сказать что React привносить магию в концепцию шаблонирования, а например, node.js в концепцию CGI =) Про метод change — если вы начинаете додумывать и программировать в уме то делайте рефакторинг там же и подписывайтесь на конкретный todo, а не на его название описание и название, тогда будет одно событие. У автора же цель показать как это работает, а не додумать за вас все возможные добавления методов в API
на счет того, что статьи описывают идеи я понимаю, но ведь описано не очень хорошо, как писал раньше: то через апи, то через кишки (хотя, судя по тому, как пишут код в доках mobX — это их путь).

но хорошо, пойдем по пути документации: https://mobxjs.github.io/mobx/refguide/observable.html
аннотация/декоратор вешается только на массив, логично ждать срабатывания только, если изменится состояние структуры массива (добавили, удалили, заменили элементы).
но события публикуются и при изменении данных, которые лежат в массиве.
а соответственно, если необходимо изменить 2+ поля в данных — это породит 2+ событий, обойти это можно только если никогда не изменять данные, которые уже лежат в массиве: пересоздавать + перезаписывать данные.

> если вы начинаете додумывать и программировать в уме то делайте рефакторинг там же и подписывайтесь на конкретный todo
на конкретный туду — не исправит проблемы, зато кода будет в разы больше и он будет в разы хуже.
ps: а разве программировать можно как-то иначе, кроме как в уме? херяк-херяк, авось заработает — это плохой подход.
Вы совершаете ту же ошибку что и автор — бегло смотрите доки) Ну или в доках этот момент явно не прописан. Есть модификатор asFlat, который позволяет подписываться только на изменение массива — добавление/удаление элементов, при этом не слушая их «дочерние» изменения.

Про программирование в уме — я прошу прощения если это прозвучало грубо, просто автор привел конкретный пример, можно долго фантазировать «а если бы» но это уже будет другой пример =) Из серии «а если поставить там-то точку с запятой то будет SyntaxError».
Спасибо! Да, этого я не досмотрел. С такой возможностью действительно все будет отлично.

Что-то уже не найти asFlat по ссылке.

Теперь это observable.shallow

Возможно по такому небольшому обзору это не очень понятно, но mobx/mobx-react действительно очень полезная вещь, и она набирает популярность. Если вам знакома боль от кучи экшнов/редьюсеров redux-а и большого количества boilerplate кода в компонентах то посмотрите на mobx. Мы его начали использовать в крупном проекте пол года назад и не только уменьшили количество кода в целом, но и сделали его более доступным для понимания новых разработчиков разного уровня. Готов поделиться подробностями, если кому интересно, без смс и регистрации =)

Конечно интересно, рассказывайте.

А давайте вы спросите что-то конкретное? А то прям статью написать у меня к сожалению пока не получается, хотя и очень хочется.

Конкретно — хочу статью с примерами :-) Я ж не знаю как вы там всё обустроили.

Это вы конечно хитро придумали! Смысл не в том как мы все обустроили, а как какую-то вашу проблему можно решить с помощью этой библиотеки. С другой стороны если у вас проблем нет — то может оно вам и не надо)

Чтобы увидеть проблему — нужно увидеть её отсутствие. Покажите пример, не будьте голословными :-)

Конкретно. Интересует пример проекта с применением MobX. Redux нравится тем, что это архитектурное решение.

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

Спасибо за руководство к действию, но к сожалению сейчас нет времени писать статью. Отдельное вам спасибо что вы это время нашли! Если есть какие-то конкретные вопросы то я постараюсь ответить.
Sign up to leave a comment.

Articles