Mundarija (26)
- Bu darsda
- 1. Nega bu kerak?
- 2. Context + useReducer
- 2.1 Provider komponenti
- 2.2 Kim qayta chizildi
- 3. Holat va dispatch — ikki kontekstda
- 4. Custom hook + null tekshiruv
- 4.1 Muammo: jim null
- 4.2 Nega default null, "bo'sh savat" emas
- 5. Qayta chizish narxini kamaytirish
- 5.1 Qoidalar
- 5.2 Qachon o'ylash kerak
- 6. Context qachon yetmaydi
- 7. Vazifalar qadami: tema — Context orqali (2-qism) va commit
- 7.1 Tema qayerda edi
- 7.2 TemaKonteksti.tsx
- 7.3 Iste'molchi va kirish nuqtasi
- 7.4 Testlar
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): himoyalangan hook
- 2-mashq (o'rta): kim qayta chiziladi?
- 3-mashq (qiyin): AuthProvider
- 10. Real ishda
- Xulosa
- Manbalar
React Context II: useReducer bilan global holat, qayta chizish narxi va chegaralar
Qisqacha: Context +
useReducer— kutubxonasiz kichik global holat: Provider komponent reducer'ni saqlaydi va holatni bir kontekstda,dispatchni boshqasida beradi. Kontekstni to'g'ridan-to'g'ri emas,useCart()kabi custom hook orqali o'qing: Provider unutilsa, hooknullo'rniga aniq xato beradi. Narxi: Provider qiymati o'zgarsa, uni o'qiyotgan hamma komponent qayta chiziladi. Holat vadispatchni ajratish,childrenorqali chizish va kontekstlarni ma'no bo'yicha bo'lish bu narxni kamaytiradi. Tez-tez o'zgaradigan yoki katta ma'lumot uchun esa maxsus vositalar bor.
Bu darsda
useReducerva Context'ni birlashtirib, savatni butun ilovaga ulaysiz.- Holat va
dispatchni ikki kontekstga ajratib, keraksiz qayta chizishni yo'qotasiz. useCart()kabi hook bilan Provider'siz ishlatishni aniq xatoga aylantirasiz.- Kontekst qiymati o'zgarganda kim qayta chizilishini konsolda ko'rsatib bera olasiz.
- Context qayerda yetmasligini va o'rniga nima kelishini bilasiz.
Oldin bilishingiz kerak: Context I, useReducer, useSyncExternalStore, children va kompozitsiya.
1. Nega bu kerak?
«Bahor» saytida savat uch joyda kerak: tepadagi belgi ("Savatda: 2"), menyudagi «Qo'shish» tugmalari va alohida savat sahifasi. useReducer darsida savat reducer'ini yozdik, Context I da qiymatni pastga yetkazishni o'rgandik. Endi ikkalasini birlashtiramiz — natija kichik "global do'kon" bo'ladi, hech qanday kutubxonasiz.
Lekin Sardor birinchi variantni yozib, React DevTools'da "Highlight updates" ni yoqdi (React DevTools): har «Qo'shish» bosilganda menyudagi hamma tugmalar yonib-o'chdi. Ular savat tarkibini ko'rsatmaydi, faqat dispatch ni chaqiradi — nega ular qayta chiziladi?
Hayotiy o'xshatish: oshxonadagi e'lonlar taxtasi. Unga hamma narsa yozilsa (bugungi menyu, oylik, tozalash navbati), har o'zgarishda hamma xodim kelib taxtani qayta o'qiydi. Oylik o'zgargan bo'lsa ham oshpaz menyuni qayta tekshiradi. Yechim — har mavzuga alohida taxta. Kontekstlarni ham shunday bo'lamiz.
2. Context + useReducer
2.1 Provider komponenti
Naqsh uch qismdan iborat: reducer, Provider komponenti va iste'molchilar.
import { createContext, use, useReducer } from "react";
const CartContext = createContext(null);
function cartReducer(cart, action) {
switch (action.type) {
case "added":
return [...cart, action.name];
default:
throw new TypeError(`Noma'lum amal: ${action.type}`);
}
}
function CartProvider({ children }) {
const [cart, dispatch] = useReducer(cartReducer, []);
return (
<CartContext value={{ cart, dispatch }}>{children}</CartContext>
);
}
function Badge() {
const { cart } = use(CartContext);
console.log("Badge");
return <p>Savatda: {cart.length}</p>;
}
function AddButton() {
const { dispatch } = use(CartContext);
console.log("AddButton");
const add = () => dispatch({ type: "added", name: "Osh" });
return <button onClick={add}>Osh +</button>;
}
function Menu() {
console.log("Menu");
return <section><AddButton /></section>;
}
function App() {
return (
<CartProvider>
<Badge />
<Menu />
</CartProvider>
);
}Konsolda:
Badge
Menu
AddButton
Badge
AddButton
Badge
AddButtonEkranda:
Savatda: 2
Osh +CartProvider — oddiy komponent: ichida useReducer, natijasini kontekstga beradi, children ni chizadi. App uni ilovaning tepasiga qo'yadi va savat hamma joyda bor bo'ladi.
2.2 Kim qayta chizildi
Konsolni o'qiymiz. Birinchi uch qator — birinchi chizish. Keyin har bosishda ikki qator: Badge va AddButton.
| Qadam | cart |
Qayta chizildi | Nega |
|---|---|---|---|
| birinchi chizish | [] |
Badge, Menu, AddButton | hammasi yangi |
| «Osh +» | [Osh] |
Badge, AddButton | ikkalasi CartContext ni o'qiydi |
| «Osh +» | [Osh, Osh] |
Badge, AddButton | — |
Ikki narsaga e'tibor bering.
Menu qayta chizilmadi. U kontekstni o'qimaydi. U App da yaratilgan va CartProvider ga children sifatida berilgan. CartProvider qayta chizilganda children — o'sha elementlar. React ularni qayta chizmaydi, faqat kontekstni o'qiyotganlarini "uyg'otadi". Bu — children naqshining bepul foydasi (children va kompozitsiya).
AddButton keraksiz qayta chizildi. U faqat dispatch ni oladi, dispatch esa hech qachon o'zgarmaydi. Lekin kontekst qiymati — { cart, dispatch } obyekti — har renderda yangi. React Object.is(eski, yangi) qiladi, false oladi va kontekstni o'qigan hammani qayta chizadi. Kontekst "obyektning qaysi qismini ishlatding" deb so'ramaydi.
3. Holat va dispatch — ikki kontekstda
react.dev'dagi "Scaling Up with Reducer and Context" sahifasi aynan shuni tavsiya qiladi: holat bir kontekstda, dispatch boshqasida.
import { createContext, use, useReducer } from "react";
const CartContext = createContext(null);
const CartDispatchContext = createContext(null);
function cartReducer(cart, action) {
switch (action.type) {
case "added":
return [...cart, action.name];
default:
throw new TypeError(`Noma'lum amal: ${action.type}`);
}
}
function CartProvider({ children }) {
const [cart, dispatch] = useReducer(cartReducer, []);
return (
<CartContext value={cart}>
<CartDispatchContext value={dispatch}>
{children}
</CartDispatchContext>
</CartContext>
);
}
function Badge() {
const cart = use(CartContext);
console.log("Badge");
return <p>Savatda: {cart.length}</p>;
}
function AddButton() {
const dispatch = use(CartDispatchContext);
console.log("AddButton");
const add = () => dispatch({ type: "added", name: "Osh" });
return <button onClick={add}>Osh +</button>;
}
function Menu() {
console.log("Menu");
return <section><AddButton /></section>;
}
function App() {
return (
<CartProvider>
<Badge />
<Menu />
</CartProvider>
);
}Konsolda:
Badge
Menu
AddButton
Badge
BadgeEkranda:
Savatda: 2
Osh +Endi bosilganda faqat Badge qayta chiziladi. CartDispatchContext ning qiymati — dispatch ning o'zi. React uni renderlar orasida barqaror saqlaydi (useReducer kafolati), shuning uchun Object.is → true va AddButton tinch qoladi. CartContext qiymati esa cart massivi — u faqat savat o'zgarganda yangi.
| Kontekst | Qiymati | Qachon o'zgaradi | Kim o'qiydi |
|---|---|---|---|
CartContext |
cart |
taom qo'shilsa/olinsa | belgi, savat sahifasi |
CartDispatchContext |
dispatch |
hech qachon | hamma tugmalar |
Menyuda 50 ta taom bo'lsa, oldingi variantda har bosishda 50 ta tugma qayta chizilardi, endi — 0 ta.
flowchart TD
P["CartProvider: useReducer"] --> C1["CartContext = cart"]
P --> C2["CartDispatchContext = dispatch"]
C1 -->|"savat o'zgarsa"| B["Badge — qayta chiziladi"]
C2 -->|"hech qachon o'zgarmaydi"| A["AddButton — tinch"]
P --> M["Menu — children, kontekst o'qimaydi"]
M --> ADiagrammada ikki "kanal": savat kanali o'zgaradi va faqat o'z o'quvchisini uyg'otadi, dispatch kanali esa jim. Menu hech qaysi kanalga ulanmagan.
Tekshirib ko'ring:
CartProviderdaconst value = { cart, dispatch }ni komponentdan tashqarida e'lon qilsak, muammo yo'qoladimi?
Javob
Bo'lmaydi: cart va dispatch faqat komponent ichida, useReducer dan keyin mavjud. Tashqarida ular yo'q. Obyektni renderlar orasida "bir xil" saqlash uchun React'da useMemo hook'i bor (memo, useMemo, useCallback, 18-qism). Lekin ikki kontekstga ajratish — memo'siz, oddiy va tushunarli yechim.
4. Custom hook + null tekshiruv
4.1 Muammo: jim null
Kontekstlarning default qiymati — null. Kimdir Badge ni CartProvider dan tashqarida ishlatib qo'ysa-chi? use(CartContext) null qaytaradi va xato boshqa joyda chiqadi: Cannot read properties of null (reading 'length'). Xabar Provider haqida hech narsa demaydi.
Yechim — kontekstni to'g'ridan-to'g'ri emas, kichik funksiya orqali o'qish. U null ni tekshirib, tushunarli xato tashlaydi:
import { createContext, use } from "react";
const CartContext = createContext(null);
function useCart() {
const cart = use(CartContext);
if (cart === null) {
throw new Error("useCart() — <CartProvider> ichida");
}
return cart;
}
function Badge() {
const cart = useCart();
return <p>Savatda: {cart.length}</p>;
}
function App() {
return <Badge />; // ❌ CartProvider unutildi
}Konsolda:
Uncaught Error: useCart() — <CartProvider> ichidaXato darhol, aniq joyda va aniq so'zlar bilan. useCart — bu custom hook: ichida boshqa hook (use) chaqiradigan, nomi use bilan boshlanadigan oddiy funksiya. Ularni keyingi darsda chuqur o'rganamiz. Hozir bitta foydasini ko'rdik: kontekst tafsilotlari (qaysi kontekst, null tekshiruvi) bir joyda yashirindi. Komponentlar faqat useCart() deydi.
4.2 Nega default null, "bo'sh savat" emas
Context I da default qiymatni "zaxira" sifatida ishlatgan edik: createContext("uz"). Til uchun bu to'g'ri — Provider'siz ham ma'noli qiymat bor. Savat yoki foydalanuvchi uchun esa "zaxira" yolg'on bo'ladi. createContext([]) bilan Provider'siz Badge "Savatda: 0" ko'rsatardi. dispatch esa () => {} bo'lib, tugmalar jim ishlamay qolardi. Dasturchi xatoni bir necha kundan keyin, mijozdan eshitardi.
Qoida: Provider'siz ma'nosi yo'q kontekstda default — null, o'qish — tekshiruvchi hook orqali. Shunday hooklarni ko'p uchratasiz: useAuth(), useTheme(), useRouter(). Hammasi bir xil tuzilgan. AuthContext ni 18-qismda xuddi shunday quramiz.
TypeScript'da bu naqsh turni ham toraytiradi: createContext<Cart | null>(null), hook esa Cart qaytaradi — iste'molchida null tekshirish shart emas.
5. Qayta chizish narxini kamaytirish
5.1 Qoidalar
Uch usulni ko'rdik, bittasini nomladik:
| Usul | Nima beradi |
|---|---|
Provider'ni alohida komponent qilib, children olish |
Provider state'i o'zgarganda faqat iste'molchilar qayta chiziladi |
holat va dispatch ni ajratish |
faqat amal yuboradiganlar qayta chizilmaydi |
| kontekstlarni ma'no bo'yicha bo'lish (tema, savat, foydalanuvchi) | tema o'zgarsa savat iste'molchilari tinch |
qiymat obyektini useMemo bilan saqlash |
Provider egasi boshqa sabab bilan qayta chizilsa ham qiymat "o'sha" (18-qism) |
Oxirgi qator haqida qisqacha. Provider'ning egasi boshqa sabab bilan qayta chizilsa (masalan, App dagi soat), value={{ theme }} yangi obyekt bo'ladi va hamma iste'molchi qayta chiziladi. Hozircha bu sezilmaydi: React baribir otasi qayta chizilgan bolalarni ham qayta chizadi. Farq memo ishlatilganda ko'rinadi — bolani "props o'zgarmasa, qayta chizma" deb belgilasangiz, yangi kontekst obyekti bu himoyani buzib o'tadi. Shuning uchun memo bilan birga useMemo ham keladi — ikkalasi 18-qismda.
5.2 Qachon o'ylash kerak
Qayta chizish — o'z-o'zidan xato emas. React tez; kichik komponentni qayta chizish mikrosoniyalar. Mini-ilova darsida 1 000 vazifali ro'yxatda bitta katakni belgilash React'da 19–20 ms (vanilla'da 47–77 ms) olganini o'lchagan edik. Avval o'lchang: React DevTools Profiler (Performance'ni o'lchash), keyin tuzating. vazifalar kanonida tema kontekstida bitta kichik iste'molchi bor — kanon izohida yozilganidek, u yerda memo shart emas.
6. Context qachon yetmaydi
Context — yetkazish mexanizmi, holat boshqaruvchisi emas. U uch holatda noqulay:
- Tez-tez o'zgaradigan qiymat. Sichqoncha joylashuvi, har harfda yoziladigan matn, taymer. Har o'zgarishda hamma iste'molchi qayta chiziladi.
- Iste'molchiga qiymatning faqat bir qismi kerak. Kontekstda "selektor" yo'q: savatdan faqat
countkerak bo'lsa ham, savat o'zgarsa qayta chizilasiz. useSyncExternalStore darsida o'zimiz yozganuseStore(store, selector)yoki Zustand aynan shuni beradi. - Server ma'lumoti. Menyu, buyurtmalar ro'yxati — ular serverda yashaydi va kesh, qayta yuklash, eskirish kabi muammolari bor. Buning uchun TanStack Query kabi vositalar (Server state vs client state).
| Ma'lumot turi | Qayerda yashaydi | Vosita |
|---|---|---|
| bitta komponentniki | komponent | useState, useReducer |
| bir necha qo'shni komponentniki | umumiy ota | state'ni ko'tarish |
| butun ilova "muhiti" (tema, til, foydalanuvchi) | Provider | Context |
| tez-tez o'zgaradigan global holat, selektor kerak | tashqi store | Zustand, Redux, useSyncExternalStore |
| serverdagi ma'lumot | kesh | TanStack Query (19-qism) |
| ulashiladigan tanlov (filtr, sahifa) | URL | useSyncExternalStore, router |
vazifalar bu jadvalga mos: filtr va saralash — URL'da (useSyncExternalStore), vazifalar va tahrir — App dagi reducer'da, qator hodisalari va tema — Context'da.
7. Vazifalar qadami: tema — Context orqali (2-qism) va commit
7.1 Tema qayerda edi
Context I darsida qator hodisalarini Context'ga o'tkazdik. Shu branch'da (refactor/kontekst) ikkinchi qism — tema. Holat 39 da tema tanlovi uch joyga bo'lingan edi:
TemaTanlov.tsx— o'zuseStatei valocalStoragega yozish;main.tsx— ishga tushgandatemaniQolla(saqlanganTanlov())vatizimTemasiniKuzat()—matchMediatinglovchisi modulda, React'dan tashqarida;tema-tanlash.ts— yordamchilar.
Tema kerak bo'lgan ikkinchi komponent paydo bo'lsa (masalan, grafik ranglari), u tanlovni qayerdan oladi? TemaTanlov ning state'i ichkarida yopiq. Tanlov va uni o'zgartiruvchi funksiya — butun ilovaga kerak "muhit" qiymati, ya'ni Context uchun klassik nomzod.
7.2 TemaKonteksti.tsx
type TemaQiymati = {
tanlov: Tema;
tanla: (yangi: Tema) => void;
};
// null — Provider yo'q (dasturchi xatosi): useTema() aniq
// xato beradi
const TemaKonteksti = createContext<TemaQiymati | null>(null);
export function TemaProvider({ children }: { children: ReactNode }) {
// Funksiya — localStorage faqat birinchi renderda o'qiladi
const [tanlov, setTanlov] = useState(saqlanganTanlov);
// Tizim qorong'imi — brauzer biladi (matchMedia): tashqi manba
const tizimQorongi = useSyncExternalStore(
tizimgaObuna,
tizimQorongimi,
() => false,
);
// Tanlov yoki tizim o'zgarsa — <html> va panel rangi ergashadi.
// v6 da matchMedia tinglovchisi modulda qo'lda ulanardi (temaniUla)
useEffect(() => {
temaniQolla(tanlov, tizimQorongi);
}, [tanlov, tizimQorongi]);
function tanla(yangi: Tema) {
setTanlov(yangi);
temaniSaqla(yangi); // xotira — hodisada (keyingi ochilishga)
}
// React 19: <Context value> — .Provider yozish shart emas. Qiymat
// har renderda yangi obyekt — iste'molchilar ham qayta chiziladi;
// bizda bitta kichik iste'molchi, o'lchash va memo — 18-qism
return (
<TemaKonteksti value={{ tanlov, tanla }}>{children}</TemaKonteksti>
);
}
/**
* Tema konteksti. Provider ichida bo'lmasa — aniq xato (null emas)
*/
export function useTema(): TemaQiymati {
// use() — useContext kabi, lekin shart ichida ham chaqirsa
// bo'ladi
const qiymat = use(TemaKonteksti);
if (qiymat === null) {
throw new Error("useTema() — <TemaProvider> ichida chaqirilsin");
}
return qiymat;
}Fayl boshidagi importlar tushirib qoldirildi, bir nechta izoh qatori telefonga sig'ishi uchun qayta bo'lindi (matni o'sha). Bu bitta faylda butun 17-qism jamlangan:
useState(saqlanganTanlov)— lazy boshlang'ich qiymat (useState);useSyncExternalStore— tizim temasi tashqi manba (useSyncExternalStore):tizimgaObunamatchMedia(...).addEventListener("change", …)ni ulaydi;useEffect—<html data-tema>va telefon paneli rangi React'dan tashqaridagi DOM, effekt bilan sinxronlanadi (useEffect I);localStoragega yozish — effektda emas, hodisada (tanla): "foydalanuvchi tanladi" — bu hodisa (Sizga effekt kerak emas);- default
null+useTema()— bugungi naqsh.
7.3 Iste'molchi va kirish nuqtasi
TemaTanlov.tsx endi state saqlamaydi:
- const [tanlov, setTanlov] = useState(saqlanganTanlov);
+ // Tanlov — kontekstdan (17/#42): state endi TemaProvider'da
+ const { tanlov, tanla } = useTema();
function tanlandi(event: ChangeEvent<HTMLInputElement>) {
- const yangi = tanlovniOl(event.target.value);
- setTanlov(yangi);
- tanlovniSaqla(yangi); // xotira, <html data-tema>, panel rangi
+ tanla(tanlovniOl(event.target.value));
}main.tsx — ilova Provider bilan o'raldi, qo'lda tinglovchi olib tashlandi:
<StrictMode>
- <App
- boshlangichRoyxat={yukla() ?? boshlangichVazifalar}
- qoralama={qoralamaniOl()}
- />
+ <TemaProvider>
+ <App
+ boshlangichRoyxat={yukla() ?? boshlangichVazifalar}
+ qoralama={qoralamaniOl()}
+ />
+ </TemaProvider>
</StrictMode>,
…
-// Panel rangi (theme-color) — saqlangan tanlov bo'yicha; <html
-// data-tema> ni tema.js allaqachon qo'ygan
-temaniQolla(saqlanganTanlov());
-tizimTemasiniKuzat();7.4 Testlar
App.test.tsx dagi har render endi <TemaProvider> bilan o'ralgan — App ichidagi TemaTanlov Provider'siz xato berardi. jsdom'da matchMedia yo'q, shuning uchun vitest-sozlash.ts ga oddiy o'rinbosar qo'shildi: doim "yorug'" tizim, tinglovchilar hech narsa qilmaydi.
Yangi test — null tekshiruvining o'zi:
test("Context'siz qator — aniq xato (useQatorAmallari null tekshiruvi)", () => {
const jim = vi.spyOn(console, "error").mockImplementation(() => {});
expect(() =>
render(
<ul>
<VazifaRoyxati
vazifalar={vazifalar}
sorov=""
tahrirId={null}
/>
</ul>,
),
).toThrow(
"useQatorAmallari() — <QatorAmallariKonteksti value> ichida",
);
jim.mockRestore();
});vi.spyOn(console, "error") — React yiqilgan render haqida konsolga yozadi, test chiqishi toza bo'lishi uchun vaqtincha "jim" qilinadi. Haqiqiy natija (npm run test):
ℹ tests 156
ℹ pass 156
ℹ fail 0
Test Files 4 passed (4)
Tests 25 passed (25)node:test — 156, Vitest — 25 (+1). Brauzer tekshiruvi 159/159, jumladan tema bo'limi — masalan, tizim qorong'iga o'tganda panel rangi change hodisasi bilan yangilanadi (endi useSyncExternalStore orqali):
✅ tema: tizim qorong'i — panel (change hodisasi): "#14201a"
✅ tema: qorong'i saqlandi: ["rgb(20, 32, 26)","#14201a",true]Diff (ikki darsdagi o'zgarishlar birga): 13 fayl, +239 −125. Commit:
refactor: tema va qator hodisalari Context orqali8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| faqat tugmalar ham qayta chiziladi | value={{ state, dispatch }} — har renderda yangi obyekt |
holat va dispatch ni ikki kontekstga |
Cannot read properties of null iste'molchida |
Provider unutilgan, default null |
useX() hook'ida null tekshiruv va aniq xato |
| Provider'siz "bo'sh" qiymat, tugmalar jim | default [] yoki () => {} |
default null + tekshiruv |
| Provider ostidagi hamma narsa qayta chiziladi | Provider ichida bolalar to'g'ridan-to'g'ri yozilgan | Provider komponenti + children |
| har harfda sahifa sekinlashadi | tez o'zgaradigan qiymat kontekstda | lokal state yoki selektorli store |
| testda "Provider ichida chaqirilsin" | test komponentni Provider'siz chizdi | testda ham Provider bilan o'rang |
9. Mashqlar
Mashqlar kurs/mashqlar/17/42-context-reducer/ da.
1-mashq (oson): himoyalangan hook
Bo'sh joylarni to'ldiring: Provider'siz chaqirilsa, hook aniq xato bersin.
const OrdersContext = createContext([___:null]);
function useOrders() {
const orders = use(OrdersContext);
if (orders [___:=== null]) {
throw new Error("useOrders() — <OrdersProvider> ichida");
}
return orders;
}Yechim
import { createContext, use } from "react";
const OrdersContext = createContext(null);
function useOrders() {
const orders = use(OrdersContext);
if (orders === null) {
throw new Error("useOrders() — <OrdersProvider> ichida");
}
return orders;
}
function Counter() {
return <p>Buyurtmalar: {useOrders().length}</p>;
}
function App() {
return (
<div>
<OrdersContext value={["Osh", "Manti"]}>
<Counter />
</OrdersContext>
<Counter />
</div>
);
}Konsolda:
Uncaught Error: useOrders() — <OrdersProvider> ichidaBirinchi Counter ishlaydi, ikkinchisi (Provider'siz) — aniq xato. Haqiqiy ilovada Provider'ni ham alohida OrdersProvider komponentiga o'rash qoladi.
2-mashq (o'rta): kim qayta chiziladi?
Kodni ishga tushirmasdan, «Choy +» bir marta bosilganda konsolga qaysi komponentlar yozilishini ayting. Keyin Total qayta chizilishini saqlab, Counter va ChoyButton keraksiz qayta chizilmasin — kontekstni bo'ling.
import { createContext, use, useReducer } from "react";
const ShiftContext = createContext(null);
function reducer(state, action) {
switch (action.type) {
case "sold":
return { ...state, total: state.total + action.price };
default:
return state;
}
}
function ShiftProvider({ children }) {
const [state, dispatch] = useReducer(reducer, {
cashier: "Sardor",
total: 0,
});
const value = { state, dispatch }; // har renderda yangi obyekt
return <ShiftContext value={value}>{children}</ShiftContext>;
}
function Counter() {
const { state } = use(ShiftContext);
console.log("Counter");
return <p>Kassir: {state.cashier}</p>;
}
function Total() {
const { state } = use(ShiftContext);
console.log("Total");
return <p>Tushum: {state.total}</p>;
}
function ChoyButton() {
const { dispatch } = use(ShiftContext);
console.log("ChoyButton");
const sell = () => dispatch({ type: "sold", price: 5000 });
return <button onClick={sell}>Choy +</button>;
}
function App() {
return (
<ShiftProvider>
<Counter />
<Total />
<ChoyButton />
</ShiftProvider>
);
}Bosilgandan keyin konsolga qo'shilgan qatorlar: Counter, [:Total], [:ChoyButton].
Yechim
Uchalasi ham — ular bitta kontekstni o'qiydi va qiymat ({ state, dispatch }) har safar yangi obyekt. Kassir ismi o'zgarmasa ham Counter qayta chizildi.
Tuzatish: kassir (smena davomida o'zgarmaydi), tushum va dispatch — uchta alohida kontekst.
import { createContext, use, useReducer } from "react";
const CashierContext = createContext(null);
const TotalContext = createContext(null);
const ShiftDispatchContext = createContext(null);
function reducer(state, action) {
switch (action.type) {
case "sold":
return { ...state, total: state.total + action.price };
default:
return state;
}
}
function ShiftProvider({ children }) {
const [state, dispatch] = useReducer(reducer, {
cashier: "Sardor",
total: 0,
});
return (
<CashierContext value={state.cashier}>
<TotalContext value={state.total}>
<ShiftDispatchContext value={dispatch}>
{children}
</ShiftDispatchContext>
</TotalContext>
</CashierContext>
);
}
function Counter() {
const cashier = use(CashierContext);
console.log("Counter");
return <p>Kassir: {cashier}</p>;
}
function Total() {
const total = use(TotalContext);
console.log("Total");
return <p>Tushum: {total}</p>;
}
function ChoyButton() {
const dispatch = use(ShiftDispatchContext);
console.log("ChoyButton");
const sell = () => dispatch({ type: "sold", price: 5000 });
return <button onClick={sell}>Choy +</button>;
}
function App() {
return (
<ShiftProvider>
<Counter />
<Total />
<ChoyButton />
</ShiftProvider>
);
}Konsolda:
Counter
Total
ChoyButton
TotalEkranda:
Kassir: Sardor
Tushum: 5000
Choy +Kontekst qiymatlari — satr, son va dispatch: Object.is ularni qiymati bo'yicha solishtiradi. Kassir ismi o'sha satr — Counter tinch.
3-mashq (qiyin): AuthProvider
TypeScript'da kichik autentifikatsiya holatini yozing ("kim tizimga kirgan"):
- holat —
{ status: "mehmon" } | { status: "kirgan"; name: string }(holat mashinasi tafakkuri); - amallar —
loggedIn(namebilan) valoggedOut; AuthProvider—useReducer+ ikki kontekst (holat vadispatch);useAuth()vauseAuthDispatch()— null tekshiruvli hooklar;Header— "Kirish" tugmasi yoki "Salom, Malika! · Chiqish";Orders— faqat kirganlarga "Buyurtmalaringiz: 2 ta", mehmonga "Avval tizimga kiring".
Yechim
import { createContext, use, useReducer } from "react";
import type { Dispatch, ReactNode } from "react";
type Auth = { status: "mehmon" } | { status: "kirgan"; name: string };
type AuthAction =
| { type: "loggedIn"; name: string }
| { type: "loggedOut" };
type AuthDispatch = Dispatch<AuthAction>;
function authReducer(state: Auth, action: AuthAction): Auth {
switch (action.type) {
case "loggedIn":
return { status: "kirgan", name: action.name };
case "loggedOut":
return state.status === "mehmon" ? state : { status: "mehmon" };
}
}
const AuthContext = createContext<Auth | null>(null);
const AuthDispatchContext = createContext<AuthDispatch | null>(null);
export function AuthProvider({ children }: { children: ReactNode }) {
const [auth, dispatch] = useReducer(authReducer, {
status: "mehmon",
});
return (
<AuthContext value={auth}>
<AuthDispatchContext value={dispatch}>
{children}
</AuthDispatchContext>
</AuthContext>
);
}
export function useAuth(): Auth {
const auth = use(AuthContext);
if (auth === null) {
throw new Error("useAuth() — <AuthProvider> ichida");
}
return auth;
}
export function useAuthDispatch(): AuthDispatch {
const dispatch = use(AuthDispatchContext);
if (dispatch === null) {
throw new Error("useAuthDispatch() — <AuthProvider> ichida");
}
return dispatch;
}
function Header() {
const auth = useAuth();
const dispatch = useAuthDispatch();
if (auth.status === "mehmon") {
const login = () =>
dispatch({ type: "loggedIn", name: "Malika" });
return <button onClick={login}>Kirish</button>;
}
const logout = () => dispatch({ type: "loggedOut" });
return (
<p>
Salom, {auth.name}! · <button onClick={logout}>Chiqish</button>
</p>
);
}
function Orders() {
const auth = useAuth();
return auth.status === "kirgan" ? (
<p>Buyurtmalaringiz: 2 ta</p>
) : (
<p>Avval tizimga kiring</p>
);
}
export default function App() {
return (
<AuthProvider>
<Header />
<Orders />
</AuthProvider>
);
}Ekranda:
Salom, Malika! · Chiqish
Buyurtmalaringiz: 2 taauth.name faqat status === "kirgan" tekshiruvidan keyin o'qiladi — TypeScript boshqacha yozishga ruxsat bermaydi. loggedOut mehmon holatida o'sha holatni qaytaradi — qayta chizish yo'q. Haqiqiy autentifikatsiya (token, server, barcha tablarda chiqish) — React'da auth holati darsida.
10. Real ishda
- Kichik va o'rta ilovalarda Context +
useReducer— kutubxonasiz global holatning eng keng tarqalgan usuli: savat, foydalanuvchi, sozlamalar. - Kutubxonalar shu naqshni ishlatadi:
ThemeProvider+useTheme(),AuthProvider+useAuth(),QueryClientProvider+useQueryClient(). Har birida null tekshiruvli hook bor. - Performance muammolari kod ko'rigida ko'pincha "kontekst qiymati har renderda yangi obyekt" yoki "hamma narsa bitta kontekstda" deb belgilanadi.
- Intervyu savollari: "Context bilan Redux/Zustand farqi?", "Kontekst o'zgarsa kim qayta chiziladi?", "Nega state va
dispatchni alohida kontekstga qo'yamiz?", "Custom hook'da null tekshiruv nima beradi?".
Xulosa
- Context +
useReducer= kutubxonasiz global holat: Provider komponenti reducer'ni saqlaydi,childrenni chizadi. - Kontekst qiymati o'zgarsa, uni o'qiyotgan hamma qayta chiziladi;
{ state, dispatch }obyekti har renderda yangi. - Holat va
dispatchni ajrating; kontekstlarni ma'no bo'yicha bo'ling;childrennaqshi oraliq komponentlarni himoya qiladi. - Default
null+useX()hook'i null'ni aniq xatoga aylantiradi va turni toraytiradi. - Tez o'zgaradigan, selektor kerak yoki serverdagi ma'lumot — Context emas, maxsus vositalar.
vazifalarda temaTemaProvider+useTema()da:useState,useSyncExternalStore, effekt va hodisa bir faylda; commit — qator hodisalari bilan birga.
Keyingi dars: Custom hooklar I: mantiqni ajratib olish — useCart kabi o'z hooklarimizni qanday yozish, nomlash va nima qaytarishni o'rganamiz va App.tsx ni yengillatamiz.
Manbalar
- React: "Scaling Up with Reducer and Context" — react.dev/learn/scaling-up-with-reducer-and-context
- React:
useContext— "Optimizing re-renders when passing objects and functions" — react.dev/reference/react/useContext - React:
useReducer—dispatchbarqarorligi — react.dev/reference/react/useReducer
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!