Стан протык Redux Toolkit протык Context: Манэжеры стану React ад Renders і розмер пакета
Аплікэцыя для караваў, створаная з восьмі шаблонаў стану React, з мерамі па выкорыстанню ресурсаў на марныя атрыбутызацыі і размеру спакаванага пакета: што паказваюць цыфры пра Zustand і Redux Toolkit.
Крыніцы: I Built the Same React App With All 8 State Management Patterns. The Numbers Picked Three Winners.
Кальколькі React-менеджэра стану калі выбіраць? Гэтыя пытанні вылупляюцца кожныя калькі месцацоў, і ў большасці случаў ўсё залежыць ад персональных прагатункоў. Кращы адказ можна атрымаць, стварыўшы тую ж маленькую функцыяльнасць за дапамогою кожнага з популярных патэранаў, закрепіўшы ўзоры роботы за дапамогою адной спільнай сэткі тэстаў, і пасчитавшы два ключовыя параметры, якія насправды разлічаюцца: частоту перерэндэраў компонентаў і колькі кілабайтаў дадае кожны варыянт. Ёсць опис гэтага эксперымента з восьмю патэранамі — ад Zustand і Redux Toolkit да простага Context — паслявыяснення, чаму ціфры ў такім разы, і інструкцыя, якую можна выконваць з вашым сабскім менеджэрам стану.
Як арганізаваны эксперымент
Програма-тэст спецыяльна зроблена мінімальной: караван для пакупак, у яком є кнопкі «Яблоко», «Банан», показчык сумы і значак тэмы, які ніколі не зменшаецца. Кожна рэалізацыя пераканана аднаковым тэстам поведэння, які трохі разоў нажымае кнопку «Яблоко» і два разы — кнопку «Банан», і пераканваецца ў ідэнтычных рэзультатах. Калі тэст запускаецца, лічылка фіксуе кожную перакаштовку кожнага компонента. Аддзельна esbuild вылічвае, што кожны падход дадае да мініфікаўанага, запакаванага пакета, не включаючы React, таму што кожны варыянт плаціць тыя ж зусиллі.
Вось восьсьмы кандыдаты:
- іспользаванне
useState, якое перадаецца через props - Context у поўнай сувязі з
useReducer - Redux Toolkit
- Zustand
- Jotai
- Valtio
- MobX
- ручна напісаны склад, створаны на базе
useSyncExternalStore, без жадных залежнасцей
Усе васьме пасуюць па спяльным контракту, таму будзь-які разніцы тут стосуюцца толькі косцоў, а не правільнасці:
✓ lifted useState › passes the shared cart contract
✓ Context + useReducer › passes the shared cart contract
✓ Redux Toolkit › passes the shared cart contract
✓ Zustand › passes the shared cart contract
✓ Jotai › passes the shared cart contract
✓ Valtio › passes the shared cart contract
✓ MobX › passes the shared cart contract
✓ useSyncExternalStore › passes the shared cart contract
Tests 8 passed (8)
Тут пераказаны версіі бібліятэк і среда выканання, якія былі выкарыстоўваны для зьвярэння, а таксама репазітарый, які мае восьмыя реалізацыі, спяльны тэст і оба скрыпты для зьвярэння:
Tested with react@19.2.8, zustand@5.0.15, @reduxjs/toolkit@2.12.0, react-redux@9.3.0,
jotai@2.20.2, valtio@2.3.2, mobx@7.0.3, mobx-react-lite@5.0.3, node 22.
Repo: github.com/noorjsdivs/state-patterns — 8 implementations, 1 shared test, 2 measurements.
Два выбары, які параджнаяць справядлівае парабяранне
Оба яны выходзяць з багоў, якія з’яўляюцца ў рэальных прыкладах практычнага выкарыстоўвання, а не з правілаў атрыбутавання.
Па-першае, кожная рэалізацыя стварае свой хранальнік унутрь складніка, а не на рэвэлі модуля. Так кожны запуск прыкладу практычнага выкарыстоўвання пачынаецца абсалютна з нуля, і не выклікаецца ніякага вытэкання стану межы тэставанням.
Па-другое, атрыбут тэмы існуе чыста як нейкі нейтральны элемент. Ён падпісваецца на значэнне, якое ніколі не мяніцца пад час клікаў, таму будь-якая его візуалізацыя пад час тэста є марнай працай, за якую можна адпаведаць тэлькі патэрану стану.
Што навмесна не ў межах
Змаганне стосуецца толькі спільнага стану кліента. TanStack Query не ўключаны, таму што ён керуе кэшам данных сервера, што ўсунэчы новыя проблемы з іншымі правіламі. Стан URL не ўключаны, таму што панель адрес узначаецца рутэрам. Ваша прыкладна програма, верагодна, патрабуе і тое, і іншае, але ніякі з іх не конкуруець у гэтым конкрэтным заходзе.
Першы сюрпрыз: розмер коду практычна ідэнтычны
Перш чым перейсці да цікавых цифр, варта згадаць адну нудную, але важлівую деталь. Восьць реалізацый складаюцца з 39 да 58 ліній коду. Найбольш развіты варіант — Redux Toolkit з 58 лініямі — на 19 ліній дыявольшы за простейшы варіант, lifted state, які складаецца з 39 ліній. У такім масштабе аргумент пра неабходнасці шаблоннага коду, який так часта выклікае дыскусіі пра кераванне станамі, становіць лічбу 19 ліній. Справжнія разлікі знаходзяцца в іншых аспектах, і яны становяцца виднымі толькі праз аналіз.
Рэйтинг рэндэраў
Цікава колькісна інформацыя пра тое, сколькі разоў кожны элемент быў адрасаваны пад час пяці клікаў, уключаючы пачатковы запуск:
5 clicks (3 apple, 2 banana) Apple Banana Total Theme(idle)
lifted useState 6 6 6 6
Context + useReducer 6 6 6 6
Redux Toolkit 4 3 6 1
Zustand 4 3 6 1
MobX 4 3 6 1
useSyncExternalStore 4 3 6 1
Jotai 5 4 7 2
Valtio 2 2 2 1
З гэтых абзацаў выяўляюцца тры разныя історыі.
Узняты статус і Context перарысваюць усё
З узнятым useState і адним Context кожны компонент перарысваецца пасля кожнага кліку. Атрыбут тэмы перарысваўся шасць разоў, хоць яго значэнне ніколі не змінілася, а кнопка з бананам перарысваўся пасля кожнага кліку на яблоко. Гэта ўжо конкрэтны механізм за адной з распашчых паведамленняў пра тое, што Context не ўтаманавае статус. useContext падключае компонент да всего значэння Context, таму калі гэта значэнне змінюе свою ідэнтычнасць, кожны компонент, які яго викорыстоўвае, перарысваецца. Размешчэнне всего вашага статусу ў аднам Context фактычна ўзначае узняты статус з дадатковым шаром.
Звычны спосаб выправлення — раздзеліць статус на калькі Context або зробіць мемаізацыю компонентаў, але гэта тут не аналізавалася. Шырокі абзац паказвае Context такім, як яго часта пішуць: адны прадастар, які храніць аднае значэнне.
Чатыры API, аднаковы рэзультат
Redux Toolkit, Zustand, MobX і ручна рэалізацыя складу даюць абсолютна аднаковыя рэзультаты: кожны компонент атрыбуецца раз у момент стварэння і зноў толькі тады, калі змяніцца тыя даны, якія ён чытае. Їхніе API здаюцца абсалютна разнымі, але ў ўжыцце ўсе яны працуюць аднакова, таму што всі чатыры спынююцца на адной і той жа основнай ідеі. Компоненты слухаюць выбраны фрагмент стану, а не весь склад, і яны паведамляюцься толькі тады, калі гэты фрагмент змініцца. Чырэзнае розглядзенне таго, як працююць выбіранне і перакананне пра аднаковасці ў аднай з гэтых бібліятэках, можна знайсці ў стацыі пра тое, як React Redux выбірае час для ператрыбуэння.
Сумнівная низкая цифра Valtio
Столбець Valtio спачатку выглядае як зламаны лічылка: два перрэндера для кнопкі, якая нажата тры разы. Лічбы правыя. Valtio групавае паведамленні пра змяны ў кялі ля мікрозаданняў, таму калькі швыдкіх, сінхронных апдэйтаў зліваюцца ў адны перрэндер на кожны компонент, і канечныя значэнні застаюцца правымі.
Існуе важны застерэжны момент. Якщо чалавек нажымае кнопку з нормальной шыроцай, то адбываецца адны перрэндер за кожны нажым, таму што кожны нажым завершаецца раней, чым пачынаецца наступны. Адчутныя перавагі з’яўляюцца толькі тады, калі апдэйты прыходзяць пакетамі, як это бывае з паведамленнямі WebSocket, стрімуючымі дадзеннямі або запускамі падзеяў прасування. Якщо ваша аплікацыя мае такі характар, этот столбець заслуговае на прыціклівую увагу.
Постаўнае дадатковае перрэндераванне у Jotai
Jotai адрабатаваў кожны компонент адну раз больш, чым група, створаная на аднойчыні сэлектарам, укладаючы два адрабатавання для атрыбута тэмы idle. Яго раздробленасць ў правільным стане, адколькі компоненты продовжваюць реагаваць толькі на атому, якія яны викорыстоўваюць, а дадатковае адрабатаванне є аднаковым у всіх століцах. Такі патэрн указвае на якісь проблемы ў пачатковай последовасці запуску з Provider зберагача, а не на вытэк падпісаў, але точная прычына не была выявлена. Спрыяйце гэту проблему як нерашаныя пытанне, а не як вердыкт проты Jotai.
Размер пакета
Гэта тое, што кожны патэрн дадае да стиснутага пакета, урахоўваючы як сэрвак коду самага патэрна, так і яго бібліятэку, пры чым React лічыцца як зовнішняй элемент:
lifted useState 0.4 KB
useSyncExternalStore 0.5 KB (zero dependencies)
Context + useReducer 0.5 KB
Zustand 0.7 KB
Valtio 2.7 KB
Jotai 4.4 KB
Redux Toolkit 10.7 KB
MobX 13.2 KB
Сярод усіх варыянтов самы роўны мае ў 33 разы большы размер, чым самы лёгкі. Іншымі словамі, Redux Toolkit і MobX разам займаюць 23,9 КБ, тады як шасць засталых падходаў разам даходзяць да 9,2 КБ.
Інтэреснае тое, як гэты список вялоўна вяжаецца з показнікамі рэндэра. Толькі два падходы адпаведаюць стандартам ідеальнага рэндэра і маюць размер меншы за адны кілабайт: Zustand і ручна напісаныя сховішчы даных. Redux Toolkit досягае таго ж рэндэра за 10,7 КБ, а MobX — за 13,2 КБ. Гэта не абавязкова ўскладненне для іх, а проста цена, якая мае сэнс толькі тады, калі вядома, што ёю можна купіць.
Тры практычныя апцыі і які ў яных косц
Для спільнага стану кліента ў типовай аплікацыі, па рэзультатах мераванняў, варта выбраць тры інструменты.
Zustand як стандарт
Zustand дае ідеальную гранулярнасць адрадзення дадзеных за 0,7 КБ у 54 лініі, а яго API практычна не патрабуе адказаў для новага колегі: цэў хук, які прыме выбірач. У гэтым тэсте ён падабяўся за працэю да Redux пры меры розмеру прыблізна у пятнаццац разоў меньшай.
Гэты рэзультат залежыць ад аднай правілы: завжды выбіраць частку дадзеных. Калі вы викорыстоўваеце такі вызов, як useCart(s => s), компонент падключаецца да всіх дадзеных, што воспламеняе проблему Context. Эфектыўнасць вырабляецца за счытком напісання вузкіх выбірачаў, а не за счытком назвы бібліятэкі. Якщо выбірач ў кожным вызове вяртае новы об’ект або масэвую, тады таксама патрэбны помачнік для параванення на поверхневы лэвэл, інакш кожная адчынка будзе выглядаць як змянена.
Рукамі напісаны склад useSyncExternalStore
Гэты варыянт являецца найболей прыемным для эксперыменту. Полная рэалізацыя, уключаючы хранэнне, запытаеся ў 58 лініях, дадае 0,5 KB, абсалютна падходзіць па структуре да калонкі адрасавання Redux і не выклікае патрэбы ў аудыті чы ў апдэйтах залежнасцяў. useSyncExternalStore — это прымітіва, яку сам React задае для безпечнага падпісання на зовнішнія хранэннія, укладання ў ситуацыю адночаснага адрасавання. Для автараў бібліятак чы для команд, якія вярнуцца да мінімальных залежнасцяў, гэтага якраз дастатнек. Аднойчы вы стаеце власнікамі коду: якщо ў вас з’яўляюцца патрэбы, вы самі можаце стварыць інструменты разработчыка, мідлвэры чы механізмы зберагання даных.
Valtio для раптовых апдэйтаў
Механізм групавання мікрозадач у Valtio быў виміроўваны, рэальны і унікальны серед усіх кандыдатаў, а 2,7 КБ — граматычна адпаведная цена за яго. Напісанне прымітнай мутацыі на кшталт state.apples++ і бачэнне таго, як апдэюцуюцься саме тыя компаненты, якія трэба, здаецца майже занадта зручным, але пісанні елементаў на экране падтверджае, што така працэса ўсамдэлі.
Чаму іншыя праграюць у гэтым супернікаванні, хоць і не ўсуненыя
Форма тэсту мае значэнне, і яна спрыяе маленькім, простым станам. Іншыя варыянты маюць перавагі, якія гэты інструмент не можа выкарыстоўваць:
- Redux Toolkit займае 10,7 КБ, але гэта компенсуецца наявнасцю інструментаў для разработчыкаў, можлівасцю дэбагавання за дапамогою «паўтарэння часу», мідлвэраў і стандартаў, якія працуюць у вялікіх організацыях. Калі ў однай базе коду працуе пяцьдзесят разработчыкаў, гэтыя стандарты стаюць справжнім продуктом.
- Модэль MobX з аб’ектамі-спостерыбельнымі є найэфектывнейшым у коде, дзе многа класаў, а такога коду ў гэтым дапытчыку няма.
Правіло дизайна, якое не залежыць ад прыязні
Кожная здесь реалізацыя стварала свой склад унутрошка компонента. Склад, заданы на рэвэле модуля, застаецца пасля зняцья компонента з экрана, і самэй так шопінг-карты застаецца пасля выйшчы з акаунта і прыме наступнага чалавека, які заўязваеся на тым жа прыстрое. Гэта таксама спрычынае перасылку даных стану между тэстамі і между запытамі пад час выканання на серверы. Якшто вы выберазеце адну правілу перагляду коду з гэтага параганення, нехай гэта будзе гэтае: обмежыце дасвічанне складоў дрэвам компонентаў, зазвычай ствараючы іх у прадастчыку, якщо толькі вы не хочаце намерна стварыць глобальны дасвічанне.
Адрабатка таго ж конкурсу у вашай сабе аплікацыі
Вы можете перадаць гэтыя метрыкі для вашага сабе стану за аднаго дня:
- Выберыце найбольш спрэчлівы элемент спільнага стану у вашай аплікацыі і стварыце навкола яго каскад з чатырох компонентаў: два компоненты, якія запішуць даны, адны, які прыняме вырахаваныя даны, і адны пасляднік, які не выкананыя нічога.
- Дадзіце лічылак перадрукавання кожнаму компоненту. Об’ект на рэвэле модуля плюс адна вызовка
track()у тэле кожнага компонента займае апошнія дзесяць ліній коду. Лічыба «сторонніка» ўособлівае вашу «падатак на Context», яка вылічваецца, а не прыпускаецца. - Вылічыце кожны кандыдат за дапамою
esbuild --bundle --minify, пазначыўшы React як зовнішні элемент. Це займае калькі секунд і дадае столбець з кілабайтамі ў аналізе. - Якщо вы викорыстоўваете Context, а «стороннік» зноў перадрукуець контэнт, або раздзеліце Context, або перайдзіце на хранэнне на адной заснованай на селектарах, пасля чаго зноў запрацюйце лічылак і уключыце цыфры до і пасля ў вашу прасбу пра падтрымку.
- Паўтарыце весь цей процес, калі React Compiler стане частью вашага процеса будавання, таму што аўтаматычная мемаізацыя можа сутэйна змяніць рядок з паднятым станом.
Адкрытыя пытанні
Застаўся два нераспрацаваныя элементы. Меншы з іх ёсць прычыной дадатковага атрыбута Jotai. Большы — це React Compiler. У рядку lifted-state показаны 6/6/6/6 самэўсёлкі таму, што ніч гэтага рядка не ўрахоўваецца як мемаізаванае, а мемаізацыя — гэта самэўсёлкі тое, што автаматызуе компілятор. Чы будзе гэты рядок прыродна адпавядаць хранальнікам, заснованым на селектарах, у справжнім коде, а не толькі ў дэманстрацыі, — гэта явны наступны эксперымент.
Што дае падсчыт атрыбутаў атрыбутаў і розмера пакетаў
- У малых масштабах разлікі ў базовым коде між бібліятэкамі стану є занедбанымі; самэўсёлкі поведзення атрыбутаў і розмер пакетаў — гэта тое, чым яны насправды разлічаюцца.
- Одна Context, якая храніць змінюючыся стан, перзапускае атрыбуты ўсіх корыстувачаў, укладаючы ў гэта і компоненты, якія ніколі не чытаюць зміненых значэнняў.
- Магазіны, які базуюцца на селектарах, чыя бы то ні было Redux Toolkit, Zustand, MobX або ручна рэалізацыя
useSyncExternalStore, выкарыстоўваюць аднойчы той жа эфектыўны патэрн атрыбутавання. - Zustand і ручна напісаны магазін даўаюць такую працэздатнасць за менш чым кілабайт; большы бібліятэкі збіраюць такі розмер завдзяк інструментам і стандартам, а не процесу атрыбутавання.
- Метод батчавання у Valtio є корыстны для стрымовых потокаў апдэйтаў, а не для звычайных клікаў.
- Ствараюце магазіны ўнутрь дрэва компанентаў, каб стан не могаў перазіць сеанс, які яго створыў.