javascript

Компонент сам себе стор, а внешний стор это антипаттерн

  • пятница, 5 июня 2026 г. в 00:00:05
https://habr.com/ru/articles/1043754/

Здравствуйте, товарищи! Эта статья про state management во фронтенде, с непривычного угла. За два года я ни разу не написал ни одного редьюсера. Расскажу, как я к этому пришёл, что слышу со стороны про сторы, и почему вам, особенно если вы только заходите во фронтенд, не надо учить Redux в принципе.

Сначала немного о себе, потом про то, что такое стор и почему я считаю его антипаттерном. Затем покажу, как без него живёт моё реальное приложение. И разберем возражения

Пара слов о себе

Меня зовут Кирилл. Пишу на $mol, это такой реактивный фреймворк, который сделал @nin-jin. Когда-то давно я работал на Vue 2 ( месяца два ), без всякого стора, компонент, props, эмитов. Vue не учил, просто правил баги на сайте.

Не так давно переехал в питер, начал ходить на митапы, и увидел статьи Димы Карловского, ну и лично с ним познакомился тоже. Меня сильно зацепило то что в его статьях есть глубокое понимание, а главное - технические анализы ( чего больше нигде в индустрии нет ).

Решился попробовать $mol. Далеко не с первого раза понял, как читать, и что такое вообще view.tree ( это абстракция над html, которая транспилируется в самые обыкновенные js классы ). В общении со знакомыми фронтами замечаю, что концепции, на которые они жалуются, в $mol не существуют. Не “решены проще”, а физически не нужны.

Стор одна из таких концепций. Я считайте что сразу начал писать на $mol’e и поэтому не набрался вредных привычек не написал ни одного dispatch. И ничего не потерял.

Стор глазами стороннего наблюдателя

Я состою в чатике с фронтами, они там в основном используют React, Angular и хвалят Vue)

Пару цитат оттуда:

Мы используем редакс, у нас стор хранит в себе все данные приложения, и при запуске devtools он падает, потому что не смог через девтулзы пропустить всё то говно, которое они туда накидали.

Я просто считаю, что нехуй тянуть в глобал стор каждую хуйню и превращать его в советский балкон.

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

Советский балкон хаххахаха Типичный Redux-стор превращается в советский балкон. Туда сваливается всё подряд.

Если посмотреть в стейт типичного Redux-приложения, то выглядит это примерно так:

{
    auth:     { ... },
    entities: {
        posts:    { byId, allIds },
        users:    { byId, allIds },
        comments: { byId, allIds },
    },
    ui: {
        sidebar:   { open: false },
        modal:     { type: null, payload: null },
        forms:     { register: { email: '', password: '' } },
        hover:     { itemId: null },         // ← hover, серьёзно
    },
    pagination: { posts: { page: 1, total: 50 } },
    cache:      { ... },                      // ← RTK Query
}

Hover лежит в глобальном сторе рядом с access-токеном пользователя. Состояние формы регистрации тоже там. Всё в куче опять же. Парировать бардак тем что “это задача програмиста не устраивать бардак” не стоит, если так делают значит так делать проще, и так будут делать всегда. Технология должна упрощать хорошие практики, и делать затруднительными плохие, а не наоборот.

Зачем вообще нужен стор? Компонент же сам должен хранить своё состояние.

Был на «Фронтенд Подлодке», смотрел лекцию про React, возник вопросец:

В реакте нужно вручную вызывать функцию, чтобы отрендерить ответ с бэка?

Ответ был, да, в общем-то, нужно. То есть в React компонент сам по себе не знает, что данные изменились. Ему надо это сказать через setState, или через подписку на стор, или через хук типа useQuery. Если вы записали значение в обычное поле объекта, React этого не заметит, ререндера не будет.

В React’e нет встроенной реактивности. То есть стор это костыль для отсутствующей фичи фреймворка. Беря React, вы собираете свой костыльный фреймворк, получается.

Vue в этом смысле умнее, там есть ref и reactive, прикрученные прямо к фреймворку. Но Vue-комьюнити всё равно к этому добавило Vuex, потом Pinia, те же сторы, только сбоку. Зачем?

В $mol всё иначе. Каждое поле, обёрнутое в @$mol_mem, автоматически уведомляет компоненты, которые его читают. Изменил, и DOM обновился. Без подписок, без диспатчей, без хуков. Так должна выглядеть “реактивность из коробки”.

Eщё цитата:

Я только щас понял что [коллега] написал, data из квери-хука не будет в сторе храниться, пока ты туда не засунешь вручную.

То есть есть Redux-стор, есть useQuery из TanStack, и они не связаны между собой. Запрос сходил на бэк, данные пришли, кэш TanStack-а их сохранил, но в Redux они не попадут, пока вы их туда руками не положите. И в Redux-комьюнити это норма.

Я уточнил:

Сейчас стандарт кэш хранить в редакс, так как 99% юзают RTK query или react query.

То есть сам Redux уже не справляется, все используют RTK Query или TanStack Query, потому что в чистом Redux работать с асинхронкой больно. И теперь у вас:

  1. Redux для глобального состояния

  2. RTK Query / TanStack Query для серверного кэша

  3. useState для локального компонентного состояния

  4. useReducer какой нибудь для среднего, между локальным и глобальным

  5. Context для прокидывания мимо props

Пять подсистем хранения состояния в одном приложении, и в каждой свои правила, API, паттерны. И разработчик каждый раз решает, куда положить очередную переменную.

У меня в $mol одна подсистема, реактивные поля компонента, и всё. Локальное лежит в компоненте, общее поднимается в общего предка, глобальное в синглтоне App. Где живёт состояние, диктует дерево компонентов, а не выбор библиотеки.

Недавно я запилил проект Blitz, это аналог Kahoot, онлайн квиз в реальном времени. Хост заводит сессию, игроки заходят по коду, отвечают на вопросы, видят баллы в реальном времени. Без бэкенда, всё через Giper Bazy, которая сама синкает данные между устройствами.

Там реализовано: блог, профили, лобби, мультиплеер, реакции. По хорошему, поле для стора огромное. В React-варианте этого приложения был бы солидный slice на сессию, slice на игроков, slice на текущий вопрос, slice на UI, slice на профиль, плюс RTK Query на серверные данные. Я думаю, файлов было бы под сотню.

Но у меня сторов нет.

Вот, например, как описан игрок:

export class $bog_blitz_player extends $giper_baza_dict.with({
	Score: $giper_baza_atom_real,
	Name: $giper_baza_atom_text,
	IsHost: $giper_baza_atom_bool,
	Avatar: $giper_baza_atom_link_to(() => $giper_baza_file),
	Answer_land: $giper_baza_atom_text,
}) {}

Это схема данных. Я говорю, игрок состоит из счёта, имени, флага «хост ли он», аватарки и ссылки на отдельный land с его ответами. Никаких actions, reducers, selectors, просто описание полей.

В коде я с ним работаю так:

const player = $bog_blitz_player.of(player_id)

player.Name() // ← чтение имени
player.Score(42) // ← запись счёта
player.Score() // ← чтение счёта
player.IsHost() // ← реактивная подписка на флаг

Хост на одном устройстве пишет player.Score( 42 ). Через секунду на устройстве игрока обновляется счёт сам, потому что Giper Baza засинкала изменение, а $mol узнал что данные поменялись и перерендерил view. Никакого диспатча, никакого socket.on('score-update', ...), никакой ручной синхронизации.

А вот так устроен переход между экранами:

screen( next?: string ) {
    if( next !== undefined ) {
        this.mobile_menu_showed( false )
        if( next === 'lobby' ) {
            this.$.$mol_state_arg.value( 'quiz', null )
        }
    }
    return this.$.$mol_state_arg.value( 'screen', next || undefined ) || 'admin'
}

$mol_state_arg это двусторонняя связь поля с query-параметром URL.

Что у меня лежит локально в самих компонентах:

  • открыто ли мобильное меню

  • какой пункт в навигации сейчас наведён

  • содержимое формы создания квиза до отправки

  • развёрнут ли блок с правилами

Всё это не существует на уровне общего стора. Оно живёт там, где используется. А когда компонент уничтожается, оно само себя удаляет.

Даже лучшим техническим решением сложно по-настоящему искренне заинтересовать кого то, я пытался. Основная претензия, синтаксис view.tree:

Какой view.tree ужасный.

Это вообще не похоже на JSX.

Я не хочу учить новый DSL.

Я точно знаю что это дело привычки, не более. view.tree это отличное решение на самом деле, а JSX это HTML, прикрученный к JavaScript со всеми его недостатками.

Через две недели работы с view.tree вы перестаёте его замечать. Через месяц вы понимаете, что он строже JSX. Нельзя случайно засунуть в дерево что-то лишнее, нельзя написать «логику в шаблоне», нельзя сделать typo в имени компонента, его проверит компилятор. Это тот же контраст, что между TypeScript и JavaScript: первое строже, второе свободнее, и через месяц TypeScript-а вы не хотите обратно.

Ещё частые возражения:

«А что с SSR?» Для статического prerender’а я написал GitHub Action, для динамического нужно делать что-то своё.

«Подходит ли оно для командной разработки?» Более чем, даже лучше всех остальных. Иерархия owner’ов в $mol жёстче любого Redux-style-guide, новый человек физически не может «случайно положить state не туда». Недавно как раз тут в чатике обсуждали в сравнении с fsd

«А найм?» Берёте любого TypeScript-разраба, который понимает классы и реактивность. Онбординг 2-4 недели. Это меньше, чем разобраться в чужой React-кодбазе с её Redux/RTK/Zustand/TanStack-зоопарком. Чем опытнее разраб на реакте, тем меньше он желает писать на реакте.

Если вы только заходите во фронтенд, не учите Redux. Не учите Zustand, Pinia, Jotai, MobX, RTK Query, TanStack Query. Потому что если вы возьмёте $mol, вам не понадобится ничего из этого. Вы не будете писать редьюсеры, потому что компонент сам себе редьюсер. Вы не будете писать селекторы, потому что геттер компонента это и есть селектор. Вы не будете писать async middleware, потому что $mol_fetch работает синхронным кодом. Вы сэкономите примерно полгода обучения и потом будете писать в три раза меньше кода.

Мне повезло натолкнуться на $mol, я сразу начал с хороших решений и практик. Не научился плохому. И когда мои знакомые в чате жалуются на “советский балкон” в Redux-сторе, на упавшие devtools, на муторные интеграционные тесты с моками, я раньше постоянно невпопад упоминал мол и всех раздражал :) Сейчас упоминают меня) Eще раз, тут не я молодец, просто фреймворк такой.

Если вы бывалый React-разраб, и дочитали до этой строки, спасибо. Я не буду вас уговаривать переучиваться, потому что у вас есть проекты, команда, инерция, найм и прочая бытовая правда. Но на pet-проект возьмите $mol на выходных, посмотрите, как это, когда стор не нужен. Может, понравится. К тому же у меня есть скил для нейронки, которая поможет разобраться:

npx skills add b-on-g/mol_skill --all -g

А если вы новичок, и фронтенд для вас ещё чистый лист, то всё ещё проще. Идите на mol.hyoo.ru, почитайте статьи Димы Карловского, приходите пообщаться на @PiterJS. И начните сразу с хорошего, как когда-то начал я.

Спасибо за внимание, товарищи. Жду вопросы и возражения в комментариях.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Опять реклама мола?
0%Да0
0%Нет0
0%Ну если это реклама, тогда любая статья с названием технологии реклама0
Никто еще не голосовал. Воздержался 1 пользователь.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Ждешь следующего PiterJs?
0%Буду, есть что сказать лично0
0%а это где?0
Никто еще не голосовал. Воздержался 1 пользователь.