Комментарии 40
Круто, не правда ли? report вызывается автоматически, синхронно, без утечки промежуточных значений.
Не очень круто. Обычно всё-таки промежуточные состояния не имеют никакого интереса и синхронное обновление состояний по всем промежуточным значениям приводит лишь к лишним тормозам.
В конструкторе класса мы создали маленькую функцию, которая выводит отчет, и обернули ее в autorun. Он создаст реакцию, которая запустится единожды, и после этого будет автоматически перезапускаться всякий раз, когда отслеживаемые данные внутри функции изменятся.
В какой момент она перестанет это делать?
Если нужно, чтобы отслеживание функции прекратилось в какой то момент, для этого есть функция when
https://mobxjs.github.io/mobx/refguide/when.html
Не очень круто. Обычно всё-таки промежуточные состояния не имеют никакого интереса и синхронное обновление состояний по всем промежуточным значениям приводит лишь к лишним тормозам.
Это предусмотрено: если обернуть мутирующий код в декоратор action (или transaction) — то реакции вызовутся не сразу, а при выходе из функции.
Так и было реализовано в KnockOut. Проблема только в том, что сначала на эту тему не заморачиваются, а когда кода становится много и начинает припекать, начинают везде расставлять эти костыли. Это как с экранированием — оно должно по умолчанию работать. А вот если где-то не надо, то уже по явному требованию разработчика.
Вообще-то в KnockOut изначально было реализовано обновление всего синхронно и сразу же, а отложенные обновления ввели только в версии 3.4, да и то значением по умолчанию не является. Кроме того, в KnockOut зависимые значения (pureComputed) и реакции (computed) выполняются вперемешку, в то время как mobX их строго различает.
Кстати, откладывание событий для коллекций и вовсе не предусмотрено — а значит, биндинг foreach отрабатывает синхронно всегда. Да вы сами в своем бенчмарке на этот эффект натыкались же!
В 3.4 сделали возможность сделать по умолчанию все вычисления отложенными. А до этого — приходилось в каждое узкое место вставлять троттлинг.
Чем "зависимые значения" отличаются от "реакции" и что плохого в том, что они исполняются вперемешку?
Не, я не натыкался. У меня данные целиком менялись за раз.
Троттлинг — это троттлинг. Отложенные вычисления — это более простой механизм. Они отличаются как таймеры и микротаски.
Чем "зависимые значения" отличаются от "реакции" и что плохого в том, что они исполняются вперемешку?
Зависимые значения — это чистые функциональные зависимости. Реакции имеют побочные эффекты. Их вычисление вперемешку плохо тем, что пользователю показываются промежуточные значения. Что одновременно ухудшает впечатления от программы и замедляет рендер.
Пользователь ничего не увидит до завершения обработчика события.
Реакции получается надо просто первыми обрабатывать. Реакции лучше вообще искоренять, чтобы не было лишних пересчётов.
Да с фига ли реакции — первыми? У нас задача минимизировать побочные эффекты, а не максимизировать!
Искоренить их не получится. Потому что любой рендер — это реакция. И запрос на сервер — тоже реакция.
Ну так они же могут что-то поменять и придётся перевычислять зависимые значения.
В общем, я понял, это такое обновление по слоям, но слоя всего 2. К сожалению это не работает. Даже если раскидывать по куче слоёв в зависимости от глубины зависимостеи — тоже не работает. Лучше всего — обновлять в порядке, в котором зависимый обращался к зависимостям.
Слоев в MobX не два, а неограниченное количество, лишних пересчетов нигде не делается.
Достигается это через ленивость. Как только какое-то базовое значение меняется — все зависимые от него значения переходят в состояние «могло быть изменено». При попытке получить значение они проверят свои зависимости на предмет изменений, что может привести к рекурсивному вычислению зависимостей.
Есть ли там разделение состояний "точно не актуально, нужен пересчёт" и "возможно не актуально, нужно проверить не изменилась ли хотя бы одна зависимость"?
Когда меняется базовое значение — все его производные, включая косвенные, переходят в POSSIBLY_STALE.
Когда некоторое зависимое значение пересчитывается, оно сравнивается с предыдущим. Если изменилось — то все его прямые производные оказываются в состоянии STALE.
Забавно получается, 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, когда у вас ре-рендер происходит не на каждый чих стора, а при изменении конкретных данных, которые использует компонент
Это в приложениях уровня TODO, а в реальных проектах с сотнями компонент и десятками сторов так никто не делает, иначе отладка становится невыносимой и рендер/сравнение виртуального дома становится дороже чем обновить реальный. Чтобы этого небыло, применяют техники, когда ждут выполнения всех асинхронных запросов в экшене и или тому подобное (можно думать об этом как о транзакциях) — это возможно и в redux и в relay
У redux другая болезнь, в реальном, как вы говорите, приложении, у вас либо мало компонентов подписаны на единственный стор и тогда у вас боль с пробрасыванием данных через props или context, либо подписано много компонентов и тогда на каждый чих стора у вас… тадам — ре-рендеры, которые надо вручную обходить через shouldComponentUpdate =)
с shouldComponentUpdate отдельная песня, учитывая что данные обычно нормализуют их потом приходится денормализовывать, а js, гад, часто выдает как результат этого всегда новые массивы и обьекты.
Вот чтобы избавить от этой боли и был создан mobx, и это не имеет ничего общего с ужасом типа Knockout, имхо.
Я бы формулу написал по-другому.
Knockout.js + ES6 — View = 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».
но хорошо, пойдем по пути документации: https://mobxjs.github.io/mobx/refguide/observable.html
аннотация/декоратор вешается только на массив, логично ждать срабатывания только, если изменится состояние структуры массива (добавили, удалили, заменили элементы).
но события публикуются и при изменении данных, которые лежат в массиве.
а соответственно, если необходимо изменить 2+ поля в данных — это породит 2+ событий, обойти это можно только если никогда не изменять данные, которые уже лежат в массиве: пересоздавать + перезаписывать данные.
> если вы начинаете додумывать и программировать в уме то делайте рефакторинг там же и подписывайтесь на конкретный todo
на конкретный туду — не исправит проблемы, зато кода будет в разы больше и он будет в разы хуже.
ps: а разве программировать можно как-то иначе, кроме как в уме? херяк-херяк, авось заработает — это плохой подход.
Про программирование в уме — я прошу прощения если это прозвучало грубо, просто автор привел конкретный пример, можно долго фантазировать «а если бы» но это уже будет другой пример =) Из серии «а если поставить там-то точку с запятой то будет SyntaxError».
Конечно интересно, рассказывайте.
Конкретно — хочу статью с примерами :-) Я ж не знаю как вы там всё обустроили.
Конкретно. Интересует пример проекта с применением MobX. Redux нравится тем, что это архитектурное решение.
Пишите статью "Как мы перешли на mobx и избавились от боли".
А там приведите, примеры, как у вас устроено приложение сейчас.
Почему так устроено.
Как было до этого.
Какую проблему вы пытались решить этим переходом.
Mobx — управление состоянием вашего приложения