Для начала mobx, несмотря на то что его сравнивают с другими библиотеками как библиотека для управления состоянием, не предоставляет практически никаких удобств для работы с состоянием за исключением вызова обновления компонентов реакта после того как меняется свойство помеченное @observable декоратором. Мы можем легко выбросить mobx убрав все @observable и @observer декораторы и получить работающее приложение, добавив всего одну строчку update() во конце всех обработчиков событий где мы меняем данные состояния которые выводятся в компонентах.  а функция update() просто вызовет "перерендер" реакт-приложения и благодаря виртуальному думу реакта в реальном думе применится только diff изменений Говорить что mobx это целый стейт-менеджер потому что позволяет сэкономить одну строчку update() в обработчиках как-то чересчур.
 То есть mobx это не та библиотека которая упрощает работу с состоянием — так в чем его основная задача? Его основная задача — это точечное обновление компонентов, а именно — вызывать обновление только тех компонентов которые зависят от данных которые поменялись.
В примере выше каждый раз когда меняется любые данные в приложении мы выполняем "перерендер" (сравнение виртуального дума) всего приложения, вызывая ReactDOM.render(<App>, document.getElementById('root')) в функции update() и, как можно догадаться, это влияет на производительность, и на больших приложениях интерфейс неизбежно будет тормозить.
 И вот эту проблему как раз и решает библиотека mobx и часть библиотеки redux. Но давайте попробуем решить задачу точечного обновления компонентов не беря во внимания эти две библиотеки.
 Первый подход — это воспользоваться иммутабельностью и двоичным поиском — если каждое обновление состояния будет возвращать новые объекты данных которые изменились и всех родительских объектов (для случая когда состояние имеет иерархическую структуру) то тогда мы можем добиться почти точечного обновления компонентов путем сравнения ссылок на предыдущее и новое состояние и пропускать все поддеревья компонентов данные которых не изменились (newSubtree === oldSubtree) и в результате мы обновим наше приложение вызовав перерендер только нужных компонента сравнив при этом данные только O(log(n)) компонентов где n — это количество компонентов.
 Но у иммутабельного подхода есть пара серьезных недостатков которые накладывают ограничения на работу с состоянием. Первый недостаток — это то, что мы не можем теперь просто взять и обновить любое свойство объекта данных в приложении. Из-за требования возвращать каждый раз новый иммутабельный объект целого состояния, нам нужно вернуть новый объект и также пересоздать все родительские объекты и массивы. Например, если объект состояния хранит массив проектов, каждый проект хранит массив задач, и каждая задача хранит массив комментариев:  То для того чтобы обновить текст у объекта комментария мы не можем просто выполнить comment.text = 'new text' — нам нужно выполнить сначала пересоздание объекта комментария (comment = {...comment, text: 'updated text'}), дальше нужно пересоздать объект задачи и скопировать у туда ссылки на другие комментарии (task = {...task, tasks: [...task.comments]}), дальше пересоздать объект проекта и скопировать туда ссылки на другие задачи (project = {...project, tasks: [...project.tasks]}) и в конце уже пересоздать объект состояние и также скопировать ссылки на другие проекты (AppStat = {...AppState, projects: [...AppState.projects]}).  Второй недостаток — это невозможность хранить в состоянии объекты которые ссылаются друг на друга. Если нам где-то в обработчике компонента  нужно получить проект в котором он находится задача — то мы не можем при создании объекта просто присвоить ссылку на родительский проект — task.project = project потому что необходимость при иммутабельном подходе возвращать новый объект не только задачи но и проекта приводит к тому что нам нужно обновить все остальные задачи в проекте — ведь ссылка на объект проекта поменялась, а значит нужно выполнить обновление всех задач, присвоив новую ссылку, а обновление как мы знаем нужно выполнить через пересоздание объекта, а если задачи хранят комментарии, нам нужно выполнить пересоздание всех комментариев, потому что они хранят ссылку на объект задачи, и так рекурсивно мы придем к пересозданию всего состояния и это будет ужасно медленно.

В итоге нам приходится либо каждый раз изменять пропсы вышестоящих компонентов чтобы передать нужный объект, либо вместо ссылок на объект сохранить айдишник task.project = '12345'; а потом где-то хранить и поддерживать хеш проектов по их айдишнику ProjectHash['12345'] = project; Поскольку решение с иммутабельностью имеет кучу недостатков давайте подумаем можно ли решить задачу точечного обновления компонентов другим способом? Нам нужно при изменении данных в приложении выполнить перерендер только тех компонентов которые зависят от этих данных. Что значит зависят? Например есть простой компонент комментария который рендерит текст комментариия этот компонент зависит от comment.text и его нужно обновить каждый раз когда меняется comment.text. Но также если компонент выводит <div>{comment.parent.text}</div> но теперь нужно обновлять компонент каждый раз когда изменится не только .text но и .parent. Решить эту задачу мы можем не применяя никакого иммутабельного подхода а задействовав возможности геттеров и сеттеров javascript и это второй из известных мне подходов решить задачу точечного обновления ui.
 Итак, мы можем поставить на сеттер функцию которая будет выполнятся каждый раз когда выполняется присвоение нового значение и будем вызывать перерендер списка компонентов которые зависят от этого свойства. Для того чтобы узнать какие компоненты от каких свойств зависят нужно перед в начале функции render() компонента присвоить в некую глобальную переменную текущий компонент, а при вызове геттера любого свойства объекта нужно добавить в список зависимостей этого свойства текущий компонент который находится в глобальной переменной. И поскольку компоненты могут "рендерятся" древовидно надо еще не забывать возвращать назад в эту глобальную переменную предыдущий компонент. Надеюсь идею вы уловили. При таком подходе каждое свойство будет хранить массив своих зависимых компонентов и при изменении свойства будет вызывать их обновление.  Теперь для того чтобы не смешивать хранение массива зависимых компонентов с данными и для упрощения кода вынесем логику такого свойства в класс Cell, который, как можно понять из аналогии, очень похож на принцип работы ячеек в excel — если другие ячейки содержат формулы от которых зависит текущая ячейка то нужно при изменении значения вызвать обновления всех зависимых ячеек. А вот роль ячейки c формулой будет играть класс ComputedCell который наследуется от класса Cell (потому что от этой ячейки может зависеть и другие ячейки). Класс ComputedCell принимает в конструкторе функцию (формулу) для пересчета и также опционально функцию для выполнения сайд-эффектов (как например вызов .forceUpdate() компонентов) А теперь для того чтобы не выполнять каждый раз установку геттеров и сеттеров мы воспользуемся декораторами из typescript или babel. Да, это накладывает ограничения на необходимость использование классов и создание объектов не через литерал const newComment = {text: 'comment1'} а через const comment = new Comment('comment1') но зато вместо ручной установки геттеров и сеттеров мы можем удобно пометить свойство как @observable и дальше работать с ним как с обычным свойством.  А для того чтобы не работать напрямую с классом ComputedCell внутри компонента, мы можем вынести этот код в декоратора @observer, который просто оборачивает метод render() и создает при первом вызове вычисляемую ячейку, передавая в качестве формулы метод render() а в качестве функции-реакции вызов this.forceUpdate() (в реальности нужно еще добавить отписку в методе componentWillUnmount() и некоторые моменты правильного оборачивания компонентов реакта, но оставим пока для простоты понимания такой вариант) и будем использовать как  Ссылка на демку В нашем примере есть один недостаток — что если зависимости компонента могут меняться? Взглянем на следующий компонент Компонент зависит от свойства user.showFirstName и дальше в зависимости от значение может зависеть либо от user.firstName либо от user.lastName, то есть если user.showFirstName == true, то мы не должны реагировать на изменение user.lastName и наоборот если user.showFirstName поменялось на false то мы не должны реагировать (и делать перерендер компонента) если меняется свойство user.firstName; Этот момент легко решается путем добавления списка зависимостей this.dependencies = new Set() в класс ячейки и небольшой логики в функцию run() — чтобы после вызова render() реакта мы сравнили предыдущий список зависимостей с новым и отписались от неактуальных зависимостей. Второй момент — что если мы сразу меняем много свойств в объекте? Поскольку зависимые компоненты будут обновляться синхронно мы получим два лишних обновления компонента Чтобы избежать лишних обновлений, в начале этой функции мы можем поставить глобальных флаг а наш @observer декоратор не будет сразу вызывать this.forceUpdate() а вызовет только тогда когда мы уберем этот флаг. И для упрощения мы вынесем эту логику в декоратор action и вместо флага будем увеличивать или уменьшать счетчик потому что декораторы могут вызываться внутри других декораторов. В итоге такой подход c использованием очень старого паттерна "observer" (не путать с observable RxJS) намного лучше подходит для реализации задачи точечного обновления компонентов чем подход с использованием иммутабельности.  Из недостатков можно заметить только необходимость создавать объекты не через литералы а через классы, а это значит что мы не можем просто принять какие-то данные от сервера и передать компонентам — необходимо провести дополнительную обработку данных оборачивая в объекты классов с @observable декораторами.
 Если вы разобрались в этих примерах — то поздравляю — вы теперь понимаете как работает изнутри "магия" mobx. И если не брать во внимание наличие в mobx @computed декоратора который делает умную мемоизацию и не будет пересчитывать значение несколько раз в процессе инвалидации (эта оптимизация достойна отдельной статьи) и разных хелперов то мы только что реализовали весь механизм обсерверов mobx-а и выяснили что их работа проста и предсказуема и разобрались в преимуществах подхода с обсерверами против иммутабельного подхода для реализации задачи точечного обновления компонентов react-а.