Comments 15
К Mobx рекомендую пакет, автоматический оборачивающий компоненты в observer: https://github.com/christianalfoni/mobx-react-observer
Это позволяет писать export function Component и в девтулзах всё видно без необходимости 2 раза писать имя компонента. Не возникают рандомные редкие баги когда думал что observer есть, а его нет.
Он срабатывает только на ваши компоненты (внешние компоненты в node_modules остаются прежними) + можно исключать папки. Прям колоссальное улучшение DX
я фронтенд-разработчик в компании VK
Зря вы вот это уточнение написали ;)
Автору спасибо за интересный контент. Вопрос: а как вы связываете мобх с серверным состоянием? Используете ли какие-нибудь бинды типа mobx-react-query?
Спасибо!
К Mobx рекомендую пакет, автоматический оборачивающий компоненты в observer: https://github.com/christianalfoni/mobx-react-observer
Это позволяет писать export function Component и в девтулзах всё видно без необходимости 2 раза писать имя компонента. Не возникают рандомные редкие баги когда думал что observer есть, а его нет.
Он срабатывает только на ваши компоненты (внешние компоненты в node_modules остаются прежними) + можно исключать папки. Прям колоссальное улучшение DX
Концепт автооборачивания действительно улучшает DX, но конкретно этот пакет тянет babel/core и его 15 зависимостей на мегабайты, что на мой взгляд довольно значительная цена. Более легковесных альтернатив с AST трансформацией не видел, но для многих проектов могут подойти трансформации кода регулярками в бандлере - не так надежно, зато 15 строк вместо гигантского дерева зависимостей.
Спасибо за статью. Пользуюсь mobx часто. Но в статье вы не описали пару проблем с которыми я сталкивался. Было бы здорово об этом написать:
1. В примерах вы присваиваете в функциях сразу значения типа store.calls++, но в консоли возникает предупреждение - что менять свойства надо в только в экшенах. Я не до конца понял почему.
2. В асинхронном примере с запросом fetch тоже самое. После await - нельзя писать код который просто стейт меняет, а нужно обернуть в runInAction, или использовать flow из того же mobx, но с ним проблемы с типизацией на Typescript.
Было бы здорово узнать подробнее о такие подводных камнях.
Да, спасибо за внимательность, допишу об этом моменте в статье!
Этот варнинг убирается через настройку
import { configure } from "mobx"
configure({
enforceActions: "never"
})Реактивность в Mobx работает синхронно - при изменении реактивных данных сразу срабатывают реакции. Значит, если вы меняете последовательно 2 переменных store.counterOne++ и store.counterTwo++ , то реактовые компоненты перерендерятся после каждой операции. И если оба этих параметра читаются в одном компоненте - будет лишний ререндер.
Да, далеко не всегда это значительно влияет на производительность, но является очевидной точкой оптимизации, чтобы снизить количество ререндеров. Mobx дает для батчей runInAction и по дефолту пишет ворнинг в консоль, если эта оптимизация не используется - это скорее просто good practice, и вполне можно без этого писать приложения, отключая enforceActions. Я же предпочитаю всегда явно батчи делать и экономить ререндеры.
На Хабре было много дискуссий по этой теме, например в этой разбираются централизованные механизмы автобатчинга и их недостатки.
На крупном проекте столкнулись с большими утечками памяти в MobX (в самой последней версии). При детальном разборе оказалось, что компоненты, обёрнутые в observer, при анмаунте просто не очищались GC, они оставались в detached elements (вкладка Memory в DevTools позволяет собрать такие элементы и вызвать GC над ними). Перешли на Valtio – идейно похожий стор, проблемы с памятью просто исчезли.
Привет, все зависит от того как писать !
У нас тоже были утечки памяти, но они были связаны с тем, что в некоторые reaction не были прикинуты сигналы на удаление
reaction(
fn1,
fn2,
{
signal: this.abortSignal
}
)В общем утечек памяти в самом observer я с уверенностью могу сказать что нет, потому что у нас вкладка с фронтом (два фронта: главное приложение + iframe приложение внутри) после работы GC занимает память 84-86 МB
Важный момент: мы не используем React хуки вовсе в слое представления для БЛ (кроме юайных хуков вроде useTable), поэтому прокси не отправляются в массив зависимостей в хуках, как и не используются в замыканиях в хуках. Это может быть связано с утечками
...если интересно, можете посмотреть мои пакеты вроде mobx-view-model...
У вас там под капотом экземпляр модели создается прямо в функции рендера (см. useValue()), что может принести много неприятных сюрпризов при работе в конкурентом режиме:
В некоторых ситуациях реакт может вызвать рендер и никогда не смаунтить компонент (или вызвать рендер повторно без сохранения состояния), из-за чего у него не будут вызваны эффекты и в вашем случае очистка модели не произойдет.
Не забывайте про StrictMode, в котором эффекты и их очистка вызываются лишний раз ради чистоты
Спасибо за комментарий! По пунктам:
Порядок рендера - не проблема. useValue в проде использует useRef-паттерн ленивой инициализации, экземпляр привязан к конкретному хуку, а не к порядку вызовов. С ViewModelStore дополнительно работает пуллинг по id - повторного создания не будет.
Отброшенный рендер - актуально только для сценария без ViewModelStore, что не основной кейс. С ViewModelStore viewModels.attach() просто инкрементит ref-count, что безопасно. Без стора действительно есть ограничение при работе с Suspense - это описано в доке. Перенос mount() в Layout Effect решил бы его, но требует нетривиального рефакторинга - синхронный mount в рендере нужен для SSR и совместимости с mobx-react observer.
StrictMode - работает корректно. В проде useRef сохраняет экземпляр через unmount/remount, lastAttachedInstanceRef предотвращает повторный attach. В дев режиме useMemo может пересоздать экземпляр при remount, но пуллинг по id в сторе не даст дублирования, а без стора очистка отработает корректно. При этом лично я StrictMode не использую - на мой взгляд, инструмент, который меняет поведение в дев и не идентичен поведению в продакшне, применять нельзя.
в комментах нет главного адепта, но есть заменитель
MobX или приправа реактивности для JS