Mundarija (22)
- Bu darsda
- 1. Nega bu kerak?
- 2. Atom modeli
- 2.1 Jonli model: atom 15 qatorda
- 2.2 Jotai: atom, hosila va yozuvchi atom
- 2.3 atomWithStorage — saqlanadigan atom
- 3. Proxy modeli
- 3.1 Jonli model: Proxy bilan "o'zgartirib yoz"
- 3.2 Valtio: proxy, snapshot, subscribe
- 3.3 useSnapshot — faqat o'qilgan maydonlar
- 3.4 MobX: observable, computed, autorun
- 4. Signals g'oyasi
- 5. Kanon tajribasi: yetti variant (nega kanonga kirmadi)
- 6. Ko'p uchraydigan xatolar
- 7. Mashqlar
- 1-mashq (oson): hosila atom
- 2-mashq (o'rta): Valtio buyurtma
- 3-mashq (qiyin): o'quv modeliga hosila atom
- 4-mashq (tahlil): qaysi model?
- 8. Real ishda
- Xulosa
- Manbalar
Jotai, Valtio va MobX: atom va proxy state React'da, signals g'oyasi
Qisqacha: Zustand va Redux'da holat — bitta katta store, komponent selektor bilan bo'lak oladi. Atom modelida (Jotai) aksincha: holat mayda mustaqil bo'laklardan (atomlardan) yig'iladi, hosila atom boshqalaridan hisoblanadi, komponent faqat o'qigan atomlari o'zgarganda chiziladi. Proxy modelida (Valtio, MobX) holatni oddiy JS obyekti kabi to'g'ridan-to'g'ri o'zgartirasiz —
cart.items.push(x)— kutubxonaProxyorqali buni sezadi va faqat shu maydonni o'qigan komponentlarni chizadi. Uchalasining umumiy ildizi — signals g'oyasi: qiymat kim uni o'qiganini o'zi biladi. Kanon tajribasida (bir xil kichik vazifa,useReducer+ Context variantiga nisbatan): Valtio +1.7 KB, Jotai +2.9, MobX +12.6 — Zustand +0.4.
Bu darsda
- Atom modelini kutubxonasiz modelda jonli ko'rasiz, keyin Jotai'da
atom, hosila atom, yozuvchi atom vaatomWithStorageni ishlatasiz. - Valtio'da
proxy,useSnapshotvasubscribebilan "o'zgartirib yoz" uslubini sinaysiz. - MobX'da
makeAutoObservable,computedvaautorunnimani avtomatik kuzatishini ko'rasiz. - Signals g'oyasini 20 qatorli modelda tushunasiz.
- To'rt modelni (store, atom, proxy, holat mashinasi) qachon tanlashni o'lchov bilan bilasiz.
Oldin bilishingiz kerak: Zustand I, Redux Toolkit I, Proxy va Reflect, useSyncExternalStore.
1. Nega bu kerak?
«Bahor» saytiga "taom konstruktori" kerak: mehmon osh tanlaydi, porsiya sonini, qo'shimchalarni (qazi, bedana tuxumi), tilni belgilaydi. Ekranda narx, kaloriya, "sizning buyurtmangiz" matni — hammasi bir-biridan hisoblanadi. Har tanlov o'z kichik komponentida.
Sardor buni Zustand'da yozdi: bitta store, o'nta maydon, o'nta selektor. Ishladi. Lekin Jasur aka so'radi: "Ertaga konstruktor uchta sahifada bo'ladi, har birida boshqa taom. Uchta store yasaysizmi?" Malika (dizayner, ekran o'quvchidan foydalanadi) esa boshqa narsani payqadi: forma maydonlari ko'p, har harfda butun konstruktor qayta chizilyapti.
Bunday "ko'p mayda, bir-biriga bog'liq qiymatlar" vazifasida boshqa modellar qulayroq bo'lishi mumkin. Hayotiy o'xshatish: Excel jadvali. A1 — narx, A2 — porsiya, A3 = A1 × A2. A1 ni o'zgartirsangiz, faqat A3 qayta hisoblanadi — butun jadval emas. Har katak — atom, formula — hosila atom. Jadvalni "kimdir A1 ni o'qiyaptimi?" deb kuzatish — signals g'oyasi.
2. Atom modeli
2.1 Jonli model: atom 15 qatorda
Jotai saytdagi natija oynasida yo'q. G'oyani kutubxonasiz o'quv modeli bilan ko'ramiz (bu Jotai emas). Atom — o'z qiymati va obunachilari bor eng kichik holat birligi:
import { useSyncExternalStore } from "react";
// O'quv modeli: atom (Jotai emas!)
function atom(initial) {
let value = initial;
const listeners = new Set();
return {
get: () => value,
set(next) {
value = typeof next === "function" ? next(value) : next;
listeners.forEach((listener) => listener());
},
subscribe(listener) {
listeners.add(listener);
return () => listeners.delete(listener);
},
};
}
function useAtom(a) {
return [useSyncExternalStore(a.subscribe, a.get), a.set];
}
const portionsAtom = atom(1);
const langAtom = atom("uz");
function Portions() {
const [portions, setPortions] = useAtom(portionsAtom);
console.log("Portions chizildi:", portions);
return (
<>
<button onClick={() => setPortions((n) => n + 1)}>
+1 porsiya
</button>{" "}
({portions})
</>
);
}
function Lang() {
const [lang, setLang] = useAtom(langAtom);
console.log("Lang chizildi:", lang);
return <button onClick={() => setLang("en")}>Til</button>;
}
export default function App() {
return (
<p>
<Portions /> <Lang />
</p>
);
}Konsolda:
Portions chizildi: 1
Lang chizildi: uz
Portions chizildi: 2
Lang chizildi: enHar komponent faqat o'z atomiga obuna. Porsiya o'zgarganda Lang chizilmadi, til o'zgarganda — Portions. Selektor yozmadik: atomning o'zi "bo'lak". Zustand'da bitta store va selektorlar bilan erishgan natijamizga bu yerda tuzilmaning o'zi bilan erishdik.
2.2 Jotai: atom, hosila va yozuvchi atom
Jotai (yaponcha "holat") — atom modelidagi React kutubxonasi, Zustand mualliflari jamoasidan (pmndrs). Kursda versiyasi 3.0.1 (kanon tajribasi). React'siz qismini — jotai/vanilla — Node'da sinaymiz:
import { atom, createStore } from "jotai/vanilla";
const priceAtom = atom(35000);
const portionsAtom = atom(1);
// hosila atom: boshqa atomlardan hisoblanadi
const totalAtom = atom((get) => get(priceAtom) * get(portionsAtom));
// faqat yozish uchun atom (amal)
const addPortionAtom = atom(null, (get, set) =>
set(portionsAtom, get(portionsAtom) + 1),
);
const store = createStore();
store.sub(totalAtom, () =>
console.log("jami:", store.get(totalAtom)),
);
console.log("boshida:", store.get(totalAtom));
store.set(addPortionAtom);
store.set(addPortionAtom);
store.set(priceAtom, 38000);
store.set(priceAtom, 38000);Toza papkada jotai@3.0.1 bilan Node 24 da:
boshida: 35000
jami: 70000
jami: 105000
jami: 114000Uch xil atom:
atom(35000)— oddiy atom: boshlang'ich qiymat.atom((get) => …)— hosila atom (derived atom): o'qish funksiyasi.get(priceAtom)— "men shu atomga bog'liqman" degan e'lon ham. Narx yoki porsiya o'zgarsa — qayta hisoblanadi, boshqa hech narsa o'zgarsa — yo'q. Excel'dagi formula.atom(null, (get, set) => …)— yozuvchi atom: qiymati yo'q (null), faqat amal. Zustand'dagi store amali kabi, lekin u ham atom.
Oxirgi ikki qatorga qarang: narx ikki marta 38 000 ga o'rnatildi, lekin jami: faqat bir marta chiqdi. Qiymat o'zgarmasa (Object.is), obunachilar bezovta qilinmaydi.
Atom — faqat tavsif (konfiguratsiya), qiymat atomda emas, store da saqlanadi: store.get(atom). React'da store'ni <Provider> beradi yoki u bo'lmasa — umumiy standart store. Bu Zustand II dagi "har so'rovga alohida store" muammosini o'zi yechadi: Next.js'da <Provider> har so'rovga yangi store yaratadi, atomlar esa modul darajasida qolaveradi.
React'da hooklar useState ga o'xshaydi:
import { useAtom, useAtomValue, useSetAtom } from "jotai";
// o'qish va yozish
const [portions, setPortions] = useAtom(portionsAtom);
const total = useAtomValue(totalAtom); // faqat o'qish
const addPortion = useSetAtom(addPortionAtom); // faqat yozishuseSetAtom — o'tgan darslardagi "faqat amal oladigan komponent chizilmaydi" qoidasi: atom qiymati o'zgarsa ham bu komponent chizilmaydi.
2.3 atomWithStorage — saqlanadigan atom
jotai/utils dagi atomWithStorage(kalit, boshlang'ich) — localStorage ga yoziladigan atom (Zustand II dagi persist ning atom varianti). Quyidagi ilovani React 19.3.0 va jotai 3.0.1 bilan Chrome 155 da ishga tushirdik:
import { atom, useAtom, useAtomValue } from "jotai";
import { atomWithStorage } from "jotai/utils";
const langAtom = atomWithStorage("bahor:til", "uz");
const portionsAtom = atom(1);
const labelAtom = atom((get) =>
get(langAtom) === "uz"
? `${get(portionsAtom)} porsiya`
: `${get(portionsAtom)} portion(s)`,
);
function Label() {
const label = useAtomValue(labelAtom);
console.log("Label:", label);
return <p>{label}</p>;
}
function Controls() {
const [lang, setLang] = useAtom(langAtom);
const [, setPortions] = useAtom(portionsAtom);
return (
<p>
<button onClick={() => setPortions((n) => n + 1)}>
+1
</button>{" "}
<button onClick={() => setLang(lang === "uz" ? "en" : "uz")}>
Til
</button>
</p>
);
}
export default function App() {
return (
<>
<Label />
<Controls />
</>
);
}«+1», keyin «Til» bosildi, so'ng sahifa qayta yuklandi. Konsol (qayta yuklashdan keyingi qism):
Label: 1 porsiya
Label: 1 portion(s)localStorage da "en" bor edi (atomWithStorage qiymatni JSON qilib saqlaydi). Lekin birinchi chizish — baribir "porsiya" (boshlang'ich "uz"), keyin darhol "portion(s)". Bu Zustand'dagi SSR miltillashiga o'xshaydi, faqat bu yerda SSR ham yo'q. Sabab: sukut bo'yicha atomWithStorage xotirani birinchi chizishdan keyin o'qiydi (server va klient bir xil boshlashi uchun). SPA'da miltillamasligi uchun: atomWithStorage("bahor:til", "uz", undefined, { getOnInit: true }) — atom yaratilganda o'qiydi. Porsiya esa oddiy atom — sahifa yangilanganda 1 ga qaytdi.
Tekshirib ko'ring:
const tableAtom = atom(4)vaconst labelAtom = atom((get) => \${get(tableAtom)}-stol`).store.set(tableAtom, 4)chaqirilsa,labelAtom` obunachisi xabar oladimi?
Javob
Yo'q. tableAtom ning qiymati 4 edi va yana 4 bo'ldi — Object.is(4, 4), o'zgarish yo'q. Hosila atom qayta hisoblanmaydi, obunachilar jim. Node misolidagi ikki marta 38 000 — xuddi shu holat.
3. Proxy modeli
3.1 Jonli model: Proxy bilan "o'zgartirib yoz"
Proxy va Reflect darsida Proxy — obyektga qilingan har o'qish va yozishni ushlaydigan "vositachi" ekanini ko'rgan edik. Proxy modelining g'oyasi: foydalanuvchi oddiy obyektni o'zgartiradi (order.portions += 1), set tuzog'i esa React'ga "o'zgardi" deydi. Kutubxonasiz o'quv modeli (bu Valtio emas):
import { useSyncExternalStore } from "react";
// O'quv modeli: proxy store (Valtio emas!)
function proxyStore(target) {
let version = 0;
const listeners = new Set();
const state = new Proxy(target, {
set(obj, key, value) {
obj[key] = value;
version += 1;
console.log(`yozildi: ${String(key)} = ${value}`);
listeners.forEach((listener) => listener());
return true;
},
});
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
const useStore = () => {
useSyncExternalStore(subscribe, () => version);
return state;
};
return [state, useStore];
}
const [order, useOrder] = proxyStore({ dish: "Osh", portions: 1 });
export default function App() {
const o = useOrder();
return (
<p>
{o.dish}: {o.portions} porsiya{" "}
<button onClick={() => (order.portions += 1)}>+1</button>
</p>
);
}Konsolda:
yozildi: portions = 2
yozildi: portions = 3Ekranda:
Osh: 3 porsiya +1order.portions += 1 — oddiy JavaScript. Hech qanday set(...) yoki dispatch(...) yo'q. React baribir bildi: getSnapshot versiya raqamini qaytaradi, u har yozuvda o'sadi. Model sodda: faqat birinchi qavatni kuzatadi (order.items.push ni sezmaydi — ichki massiv proxy emas). Haqiqiy kutubxonalar har ichki obyektni ham proxy'ga o'raydi va kim nimani o'qiganini ham kuzatadi.
3.2 Valtio: proxy, snapshot, subscribe
Valtio (fincha "davlat") — proxy modelidagi kichik kutubxona, yana pmndrs jamoasidan. Versiyasi 2.3.2. React'siz qismi — valtio/vanilla:
import { proxy, snapshot, subscribe } from "valtio/vanilla";
const cart = proxy({
items: [{ name: "Osh", price: 35000 }],
waiter: "Sardor",
get total() {
return this.items.reduce((sum, item) => sum + item.price, 0);
},
});
subscribe(cart, () => console.log("o'zgardi, jami:", cart.total));
const before = snapshot(cart);
cart.items.push({ name: "Manti", price: 30000 });
cart.items[0].price = 38000;
await Promise.resolve();
const after = snapshot(cart);
console.log(before.total, after.total);
console.log("items yangi:", before.items !== after.items);
console.log("o'zgarmagan qism:", snapshot(cart) === after);Node 24, valtio 2.3.2:
o'zgardi, jami: 68000
35000 68000
items yangi: true
o'zgarmagan qism: trueproxy(obyekt)— kuzatiladigan holat. Ichki massiv va obyektlar ham chuqur kuzatiladi:cart.items.push(…)vacart.items[0].price = …ikkalasi ham ushlandi.subscribe— ikki o'zgarish uchun bir xabar. Valtio yozuvlarni bitta mikrovazifaga yig'adi (event loop dagi mikrovazifa navbati) — shuning uchun misoldaawait Promise.resolve()bor.get total()— getter (Getter va setter) — hosila qiymat proxy ichida.snapshot(cart)— shu ondagi o'zgarmas nusxa. O'zgargan qism — yangi havola (items yangi: true), o'zgarish bo'lmasa — o'sha nusxa qaytadi (o'zgarmagan qism: true). Bu strukturaviy bo'lishish: ReactObject.isbilan solishtira oladi.
Qoida: o'zgartirish — proxy'da, o'qish (renderda) — snapshot'da. Snapshot'ni o'zgartirmang: u faqat ko'rsatish uchun, o'zgarish hech qayerga yetmaydi.
3.3 useSnapshot — faqat o'qilgan maydonlar
React'da useSnapshot(proxy) snapshot qaytaradi va render paytida qaysi maydonlar o'qilganini eslab qoladi. Quyidagi ilovani valtio 2.3.2 bilan Chrome 155 da ishga tushirdik («Osh», keyin «Malika» bosildi):
import { proxy, useSnapshot } from "valtio";
const cart = proxy({ items: [], waiter: "Sardor" });
function Waiter() {
const snap = useSnapshot(cart);
console.log("Waiter chizildi:", snap.waiter);
return <p>Ofitsiant: {snap.waiter}</p>;
}
function Count() {
const snap = useSnapshot(cart);
console.log("Count chizildi:", snap.items.length);
return <p>Savatda: {snap.items.length}</p>;
}
function Buttons() {
return (
<p>
<button onClick={() => cart.items.push("Osh")}>Osh</button>{" "}
<button onClick={() => (cart.waiter = "Malika")}>Malika</button>
</p>
);
}
export default function App() {
return (
<>
<Waiter />
<Count />
<Buttons />
</>
);
}Waiter chizildi: Sardor
Count chizildi: 0
Count chizildi: 1
Waiter chizildi: MalikaIkkala komponent bir xil useSnapshot(cart) chaqiradi — selektor yo'q. Lekin «Osh» bosilganda faqat Count (u items ni o'qigan), «Malika» da faqat Waiter chizildi. Valtio snapshot'ni ham proxy'ga o'rab, render paytidagi o'qishlarni yozib oladi (proxy-compare kutubxonasi). Bu avtomatik selektor: nimani o'qisangiz, shunga obunasiz.
Tuzog'i ham shundan: const { items } = useSnapshot(cart) dan keyin items.length ni faqat useEffect ichida o'qisangiz, render paytida o'qilmagan — Valtio uni kuzatmaydi. O'qishlar renderda bo'lsin.
3.4 MobX: observable, computed, autorun
MobX — proxy (reaktiv) modelining eng eski va katta kutubxonasi (2015). Versiyalari: mobx 7.0.6, React bog'lamasi mobx-react-lite 5.1.0. Uslubi — klasslar va "reaktiv" hisob:
import { autorun, makeAutoObservable } from "mobx";
class Cart {
items = [];
waiter = "Sardor";
constructor() {
makeAutoObservable(this);
}
add(name, price) {
this.items.push({ name, price });
}
get total() {
console.log("(total hisoblandi)");
return this.items.reduce((sum, item) => sum + item.price, 0);
}
}
const cart = new Cart();
autorun(() => console.log("ekran: jami", cart.total));
cart.add("Osh", 35000);
cart.waiter = "Malika";
cart.add("Manti", 30000);Node 24, mobx 7.0.6:
(total hisoblandi)
ekran: jami 0
(total hisoblandi)
ekran: jami 35000
(total hisoblandi)
ekran: jami 65000makeAutoObservable(this)— klassning maydonlarini observable (kuzatiladigan), getterlarini computed (eslab qolinadigan hosila), metodlarini action qiladi.autorun(fn)— funksiyani darhol bajaradi va u o'qigan observable'lar o'zgarsa, qayta bajaradi. "ekran: jami" — React komponentining o'rinbosari.- Ofitsiant almashdi (
cart.waiter = "Malika") —autorunjim. Uwaiterni o'qimagan. (total hisoblandi)har o'zgarishda bir marta:computedo'z bog'liqliklari (items) o'zgarganda qayta hisoblanadi, aks holda eslab qolingan qiymatni beradi.
React'da komponent observer(...) bilan o'raladi — u autorun ning komponent varianti. Kanon tajribasidagi MobX varianti (aynan):
// holat-mobx.tsx — MobX 7 + mobx-react-lite 5 (observable klass)
import { makeAutoObservable } from "mobx";
import { observer } from "mobx-react-lite";
import { Korinish, ishgaTushir } from "./_umumiy.tsx";
import type { Filtr, Vazifa } from "./_umumiy.tsx";
class VazifalarStore {
vazifalar: Vazifa[] = [];
filtr: Filtr = "hammasi";
constructor() {
makeAutoObservable(this);
}
qosh = (matn: string) => {
this.vazifalar.push({ id: this.vazifalar.length + 1, matn, bajarildi: false });
};
almashtir = (id: number) => {
const v = this.vazifalar.find((x) => x.id === id);
if (v) v.bajarildi = !v.bajarildi;
};
filtrla = (f: Filtr) => {
this.filtr = f;
};
}
const store = new VazifalarStore();
const Ilova = observer(() => (
<Korinish
holat={{ vazifalar: store.vazifalar.slice(), filtr: store.filtr, qosh: store.qosh, almashtir: store.almashtir, filtrla: store.filtrla }}
/>
));
ishgaTushir(<Ilova />);Tajriba eslatmasi: store.vazifalar.slice() — MobX massivi proxy, Korinish uni oddiy massiv kabi olsin deb nusxa. observer ichida bu nusxa olish ham "o'qish" — shuning uchun ro'yxat o'zgarsa, Ilova qayta chiziladi.
Tekshirib ko'ring: MobX misolida
cart.add("Choy", 0)chaqirilsa,autorunqayta ishlaydimi?totalqayta hisoblanadimi?
Javob
total — ha, autorun — yo'q. items massiviga element qo'shildi, ya'ni total ning bog'liqligi o'zgardi. U qayta hisoblanadi, natija esa o'sha 65 000. autorun faqat total ni o'qiydi. MobX computed natijasi o'zgarmaganini ko'radi va autorun ni qayta ishlatmaydi: computed qiymat teng bo'lsa, unga bog'liq reaksiyalar jim qoladi. Konsolda faqat (total hisoblandi) chiqadi, "ekran: jami" — yo'q.
4. Signals g'oyasi
Jotai'ning get(atom) i, Valtio'ning snapshot o'qishlari va MobX'ning observable'lari — bitta g'oyaning uch ko'rinishi: qiymat kim uni o'qiganini o'zi kuzatadi va faqat ularga xabar beradi. Bu g'oya signals deb ataladi. Preact, Solid, Vue (ref), Angular (signal()) va Svelte 5 ($state) uni o'z yadrosiga olgan. TC39 da (JavaScript standarti qo'mitasi) uni tilga qo'shish taklifi ham bor (proposal-signals) — hali standart emas.
Kutubxonasiz o'quv modeli — ishga tushiring:
let running = null; // hozir bajarilayotgan effekt
function signal(value) {
const readers = new Set();
return {
get() {
// kim o'qidi — yozib olamiz
if (running) readers.add(running);
return value;
},
set(next) {
value = next;
readers.forEach((effect) => effect());
},
};
}
function effect(fn) {
running = fn;
fn();
running = null;
}
const price = signal(35000);
const portions = signal(1);
const waiter = signal("Sardor");
effect(() => console.log("jami:", price.get() * portions.get()));
portions.set(2);
waiter.set("Malika");
price.set(38000);Konsolda:
jami: 35000
jami: 70000
jami: 76000effect birinchi bajarilishda price va portions ni o'qidi — ular effektni "o'quvchi" sifatida yozib oldi. waiter ni o'qimagan — uning o'zgarishi jim o'tdi. Hech qanday selektor, bog'liqliklar massivi yo'q. Haqiqiy kutubxonalar bunga hosila signallar (computed), partiyalab yangilash va eski bog'liqliklarni tozalashni qo'shadi.
React o'zi signals modelini ishlatmaydi: unda "holat o'zgardi → komponent qayta chizildi → farq hisoblandi" (Render va commit). Shuning uchun React'da signals kutubxonalari useSyncExternalStore orqali ulanadi va komponent darajasida ishlaydi. Signals'ning asosiy foydasi — mayda yangilanishlar — React'da React Compiler va selektorlar bilan qisman qoplanadi.
5. Kanon tajribasi: yetti variant (nega kanonga kirmadi)
Bu darsda vazifalar o'zgarmaydi. Redux Toolkit I darsidagi tajriba — bir xil vazifa (qo'shish, belgilash, filtr, "qoldi") yetti vosita bilan — atom va proxy modellarini ham o'z ichiga oladi. Hajmni o'sha darsda ko'rdik. Bu safar — kod qatori (bo'sh va izoh qatorlarisiz, tajriba/hajm.txt):
- Zustand+0.4 KB14 qator
- Jotai+2.9 KB19 qator
- Valtio+1.7 KB22 qator
- MobX+12.6 KB28 qator
- Redux Toolkit+8.3 KB34 qator
- useReducer + Contextasos39 qator
- XState+12.9 KB42 qator
Manba: IlmHamroh kurs tajribasi (qator — bo'sh va izohsiz; KB — Context variantiga nisbatan gzip, 2026-10-13)
Jotai va Valtio variantlari (aynan):
// holat-jotai.tsx — Jotai 3 (atomlar)
import { atom, useAtom, useSetAtom } from "jotai";
import { Korinish, ishgaTushir } from "./_umumiy.tsx";
import type { Filtr, Vazifa } from "./_umumiy.tsx";
const vazifalarAtom = atom<Vazifa[]>([]);
const filtrAtom = atom<Filtr>("hammasi");
const qoshAtom = atom(null, (get, set, matn: string) =>
set(vazifalarAtom, [...get(vazifalarAtom), { id: get(vazifalarAtom).length + 1, matn, bajarildi: false }]),
);
const almashtirAtom = atom(null, (get, set, id: number) =>
set(vazifalarAtom, get(vazifalarAtom).map((v) => (v.id === id ? { ...v, bajarildi: !v.bajarildi } : v))),
);
function Ilova() {
const [vazifalar] = useAtom(vazifalarAtom);
const [filtr, filtrla] = useAtom(filtrAtom);
const qosh = useSetAtom(qoshAtom);
const almashtir = useSetAtom(almashtirAtom);
return <Korinish holat={{ vazifalar, filtr, qosh, almashtir, filtrla }} />;
}
ishgaTushir(<Ilova />);// holat-valtio.tsx — Valtio 2 (proxy: to'g'ridan o'zgartirish)
import { proxy, useSnapshot } from "valtio";
import { Korinish, ishgaTushir } from "./_umumiy.tsx";
import type { Filtr, Vazifa } from "./_umumiy.tsx";
const holat = proxy({ vazifalar: [] as Vazifa[], filtr: "hammasi" as Filtr });
function Ilova() {
const s = useSnapshot(holat);
return (
<Korinish
holat={{
vazifalar: s.vazifalar,
filtr: s.filtr,
qosh: (matn) => void holat.vazifalar.push({ id: holat.vazifalar.length + 1, matn, bajarildi: false }),
almashtir: (id) => {
const v = holat.vazifalar.find((x) => x.id === id);
if (v) v.bajarildi = !v.bajarildi;
},
filtrla: (f) => void (holat.filtr = f),
}}
/>
);
}
ishgaTushir(<Ilova />);Jotai'da hamma narsa atom: hatto amallar (qoshAtom, almashtirAtom). Valtio'da amal — oddiy JS: holat.vazifalar.push(…), v.bajarildi = !v.bajarildi. Bu "o'zgartirib yoz" uslubi Immer bilan bir xil his beradi, lekin Immer'dagi "qoralama" chegarasi yo'q — istalgan joyda yozasiz.
Nega kanonga kirmadi: vazifalar ning client state'i — e'lon va ikki sozlama (Zustand II). Talab — marshrut action'idan React'siz yozish va selektor. Zustand buni eng kichik narx bilan bajardi (tajribada +0.4 KB, 14 qator; vazifalar build'ida — +1.3 KB) va persist, devtools tayyor. Atom yoki proxy modelining kuchli tomonlari (ko'p mayda bog'liq qiymatlar, chuqur ichma-ich obyektlar) bu ilovada yo'q.
Qachon qaysi biri — to'g'ri tanlov:
| Vosita | Qachon |
|---|---|
| Jotai | ko'p mayda, bir-biridan hisoblanadigan qiymatlar (konstruktor, filtrlar paneli, jadval muharriri); holat komponent daraxtiga yaqin, har sahifada alohida (Provider bilan) |
| Valtio | chuqur ichma-ich obyekt (sozlamalar daraxti, diagramma muharriri), "o'zgartirib yoz" jamoaga qulay; eng kichik (+1.7 KB) |
| MobX | katta domen modeli klasslar bilan (CRM, moliya), murakkab hisoblangan qiymatlar; jamoa OOP'ga o'rgangan; React'dan tashqari ham ishlatiladi |
| Zustand | bitta global store, hook'siz kirish, eng kam narx — vazifalar tanlovi |
6. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| Jotai: atom qiymati har chizishda boshlang'ichga qaytadi | atom() komponent ichida yaratilgan — har renderda yangi atom |
atomni modul darajasida (yoki useMemo) |
Jotai: atomWithStorage bilan birinchi kadr miltillaydi |
xotira birinchi chizishdan keyin o'qiladi | { getOnInit: true } (SPA'da) |
| Valtio: o'zgartirdim, ekran yangilanmadi | snapshot o'zgartirilgan, proxy emas | yozish — cart.x = … (proxy), o'qish — snap.x |
| Valtio: maydon o'zgarsa ham komponent chizilmaydi | maydon renderda emas, effektda o'qilgan | o'qishni render ichiga |
| MobX: komponent yangilanmaydi | observer(...) bilan o'ralmagan |
const C = observer(() => …) |
MobX: [MobX] Since strict-mode is enabled, changing (observed) observable values without using an action is not allowed |
observable action'dan tashqarida o'zgartirildi | metod (makeAutoObservable uni action qiladi) yoki runInAction |
7. Mashqlar
Mashqlar kurs/mashqlar/19/26-atom-proxy/ da. 1- va 2-mashq Node'da (o'z papkangizda jotai@3.0.1, valtio@2.3.2, .mjs fayl); 3-mashq — saytda.
1-mashq (oson): hosila atom
Narx 30 000, porsiya 2. Jami — hosila atom. Bo'sh joyni to'ldiring va natijani ayting:
const totalAtom = atom(
(get) => get(priceAtom) [___:*] get(portionsAtom),
);Porsiya 3 bo'lgach store.get(totalAtom) →
Yechim
import { atom, createStore } from "jotai/vanilla";
const priceAtom = atom(30000);
const portionsAtom = atom(2);
const totalAtom = atom((get) => get(priceAtom) * get(portionsAtom));
const store = createStore();
console.log(store.get(totalAtom));
store.set(portionsAtom, 3);
console.log(store.get(totalAtom));Node 24, jotai 3.0.1:
60000
90000store.set(portionsAtom, 3) dan keyin totalAtom ni hech kim "yangila" demadi — hosila atom bog'liqligi o'zgarganini o'zi biladi.
2-mashq (o'rta): Valtio buyurtma
Valtio proxy bilan buyurtma: table (4), items, get total(). subscribe har xabarda <stol>-stol: <jami> so'm yozsin. Avval osh qo'shing (keyin mikrovazifa kutish), so'ng choy qo'shib, stolni 5 ga o'zgartiring. Nechta xabar chiqadi?
Yechim
import { proxy, subscribe } from "valtio/vanilla";
const order = proxy({
table: 4,
items: [],
get total() {
return this.items.reduce((sum, item) => sum + item.price, 0);
},
});
subscribe(order, () =>
console.log(`${order.table}-stol: ${order.total} so'm`),
);
order.items.push({ name: "Osh", price: 35000 });
await Promise.resolve();
order.items.push({ name: "Choy", price: 5000 });
order.table = 5;
await Promise.resolve();Node 24, valtio 2.3.2:
4-stol: 35000 so'm
5-stol: 40000 so'mIkkita xabar: oshdan keyin bitta, choy va stol o'zgarishi — birgalikda bitta (bir mikrovazifa ichida). Ekranda ham shunday: React ikki o'zgarishni bitta chizishda ko'radi.
3-mashq (qiyin): o'quv modeliga hosila atom
«Jonli model: atom 15 qatorda» bo'limidagi atom ga hosila atom qo'shing: derived(fn, deps) — fn bog'liq atomlar qiymatidan hisoblaydi, deps — bog'liq atomlar massivi. U ham get/subscribe ga ega bo'lsin (yozish yo'q) va bog'liqlardan biri o'zgarsa obunachilarini xabardor qilsin. Ekranda: Jami: <narx × porsiya> so'm.
Ishora: derived ichida har bog'liq atomga subscribe qiling; qiymatni get() chaqirilganda hisoblang. getSnapshot bir xil kirishda bir xil qiymat qaytarsin — son bo'lgani uchun bu o'z-o'zidan bajariladi.
Yechim
import { useSyncExternalStore } from "react";
function atom(initial) {
let value = initial;
const listeners = new Set();
return {
get: () => value,
set(next) {
value = typeof next === "function" ? next(value) : next;
listeners.forEach((listener) => listener());
},
subscribe(listener) {
listeners.add(listener);
return () => listeners.delete(listener);
},
};
}
function derived(fn, deps) {
const listeners = new Set();
deps.forEach((dep) =>
dep.subscribe(() => listeners.forEach((listener) => listener())),
);
return {
get: () => fn(...deps.map((dep) => dep.get())),
subscribe(listener) {
listeners.add(listener);
return () => listeners.delete(listener);
},
};
}
const priceAtom = atom(35000);
const portionsAtom = atom(1);
const totalAtom = derived(
(price, portions) => price * portions,
[priceAtom, portionsAtom],
);
function Total() {
const total = useSyncExternalStore(
totalAtom.subscribe,
totalAtom.get,
);
console.log("Total:", total);
return <p>Jami: {total} so'm</p>;
}
export default function App() {
return (
<>
<Total />
<button onClick={() => portionsAtom.set((n) => n + 1)}>
+1 porsiya
</button>{" "}
<button onClick={() => priceAtom.set(38000)}>
Narx 38 000
</button>
</>
);
}Konsolda:
Total: 35000
Total: 70000
Total: 76000Ekranda:
Jami: 76000 so'm
+1 porsiya Narx 38 000Modelning soddaligi: deps ni qo'lda beramiz. Jotai'da get(atom) chaqiruvining o'zi bog'liqlikni e'lon qiladi (signals modelidagi running kabi) va obunalar ham dangasa: hech kim o'qimasa, hosila atom bog'liqlarga obuna bo'lmaydi.
4-mashq (tahlil): qaysi model?
Har vazifa uchun tanlang — Zustand, Jotai, Valtio yoki MobX — va asoslang:
- Onlayn "taom konstruktori": 12 ta tanlov, narx va kaloriya ulardan hisoblanadi, har taom sahifasida alohida konstruktor.
- Restoran zalining sxemasi: stollar, stullar, ularning holati — 4 qavat ichma-ich obyekt, sudrab joylash.
- Ilova bo'yi bitta "foydalanuvchi sozlamalari" va savat, marshrut action'idan yoziladi.
Yechim
- Jotai — mayda bog'liq qiymatlar (hosila atomlar), har sahifaga alohida
Provider(har taomga o'z store'i). - Valtio (yoki MobX) — chuqur ichma-ich obyektni
stol.stullar[2].band = truedeb o'zgartirish; spread piramidasi yo'q. Jamoa klasslarni afzal ko'rsa — MobX. - Zustand — bitta global store,
getState()bilan hook'siz yozish,persist.vazifalarqarori.
Har uchala javob "yagona to'g'ri" emas: hammasi har qanday vazifani bajara oladi. Tanlov — qaysi biri shu vazifada kamroq kod va kamroq tuzoq beradi.
8. Real ishda
- Jotai — Next.js loyihalarida ko'p (Provider har so'rovga store beradi), tRPC va TanStack Query bilan integratsiya paketlari bor (
jotai-tanstack-query). - Valtio — muharrir tipidagi ilovalar (diagramma, slayd, 3D sahna —
react-three-fiberbilan birga), chuqur holat. - MobX — korporativ ilovalar, ayniqsa Angular yoki klassik OOP fonidagi jamoalar; MobX-State-Tree — uning qat'iy sxemali varianti.
- Signals — React'dan tashqaridagi freymvorklar (Solid, Vue, Angular, Svelte 5) asosi. React'da Preact Signals'ning React adapteri bor, lekin rasmiy yo'nalish emas.
- Intervyu savollari: "atom va store modeli farqi?", "Valtio'da nega snapshot o'zgartirilmaydi?", "MobX
computednima beradi?", "signals nima va React undan nimasi bilan farq qiladi?".
Xulosa
- Atom modeli (Jotai): holat — mayda atomlar; hosila atom
get(...)bilan bog'liqlarini o'zi kuzatadi; qiymat store'da, atom — tavsif. atomWithStorage— saqlanadigan atom; SPA'da miltillamasligi uchungetOnInit: true.- Proxy modeli (Valtio, MobX): oddiy obyektni o'zgartirasiz,
Proxybuni sezadi; o'qilgan maydonlarga avtomatik obuna. - Valtio: yozish — proxy'da, o'qish — snapshot'da; MobX:
observable,computed,action,observer. - Signals — "qiymat o'quvchilarini o'zi biladi" g'oyasi; React yadrosi uni ishlatmaydi, kutubxonalar
useSyncExternalStoreorqali ulanadi. - Tajriba: Zustand +0.4 KB/14 qator, Valtio +1.7/22, Jotai +2.9/19, MobX +12.6/28 —
vazifalarda Zustand; boshqalari — o'z vazifasida.
Keyingi dars: IndexedDB kutubxonalari React'da: idb-keyval va Dexie — localStorage cheklovlari, katta ma'lumot va Blob'larni saqlash, useLiveQuery va sxema migratsiyasi; o'lchov bilan.
Manbalar
- Jotai: Core (
atom,useAtom,createStore), Utils (atomWithStorage) — jotai.org/docs (v3, 2026-10 holatiga) - Valtio:
proxy,useSnapshot,subscribe,snapshot— valtio.dev/docs (v2) - MobX:
makeAutoObservable,computed,autorun,observer— mobx.js.org (v7) - TC39 Signals proposal — github.com/tc39/proposal-signals
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!