14-bo‘lim
useReducer
Murakkab holatni reduktor bilan boshqarish, amallar, useState bilan taqqoslash, kontekst bilan birlashtirish va reduktorni alohida sinash.
Ushbu bo‘lim mundarijasi
Holat murakkablashganda useState chalkash bo'lib ketadi:
o'nlab o'zgartirish funksiyalari, ular orasida bog'liqlik.
useReducer boshqa yondashuv taklif qiladi: hamma o'zgarish
bitta joyda bo'ladi.
Muammo #
import { useState } from "react";
import { chiz, bos, matn } from "./dom-muhit.mjs";
function Savat() {
const [mahsulotlar, mahsulotlarniOzgartir] = useState([]);
const [chegirma, chegirmaniOzgartir] = useState(0);
const [yuborildi, yuborishniOzgartir] = useState(false);
function qosh(nom, narx) {
mahsulotlarniOzgartir((o) => [...o, { nom, narx }]);
yuborishniOzgartir(false);
}
function tozala() {
mahsulotlarniOzgartir([]);
chegirmaniOzgartir(0);
yuborishniOzgartir(false);
}
const jami = mahsulotlar.reduce((a, m) => a + m.narx, 0) - chegirma;
return (
<div>
<p id="holat">{mahsulotlar.length} ta, jami: {jami}</p>
<button id="qosh" onClick={() => qosh("Kitob", 50000)}>
Qo'shish
</button>
<button id="tozala" onClick={tozala}>Tozalash</button>
</div>
);
}
await chiz(<Savat />);
console.log(matn("#holat"));
await bos("#qosh");
await bos("#qosh");
console.log(matn("#holat"));
await bos("#tozala");
console.log(matn("#holat"));
0 ta, jami: 0
2 ta, jami: 100000
0 ta, jami: 0
Kod ishlaydi, lekin muammo ko'rinib turibdi: har bir amal uch holatni birga o'zgartirishi kerak va bittasini unutish oson.
Reduktor bilan #
import { useReducer } from "react";
import { chiz, bos, matn } from "./dom-muhit.mjs";
const boshlangich = { mahsulotlar: [], chegirma: 0, yuborildi: false };
function reduktor(holat, amal) {
switch (amal.turi) {
case "qoshildi":
return {
...holat,
mahsulotlar: [...holat.mahsulotlar, amal.mahsulot],
yuborildi: false,
};
case "chegirma":
return { ...holat, chegirma: amal.miqdor, yuborildi: false };
case "tozalandi":
return boshlangich;
default:
throw new Error(`noma'lum amal: ${amal.turi}`);
}
}
function Savat() {
const [holat, yubor] = useReducer(reduktor, boshlangich);
const jami = holat.mahsulotlar.reduce((a, m) => a + m.narx, 0)
- holat.chegirma;
return (
<div>
<p id="holat">{holat.mahsulotlar.length} ta, jami: {jami}</p>
<button
id="qosh"
onClick={() =>
yubor({ turi: "qoshildi",
mahsulot: { nom: "Kitob", narx: 50000 } })}
>
Qo'shish
</button>
<button
id="chegirma"
onClick={() => yubor({ turi: "chegirma", miqdor: 20000 })}
>
Chegirma
</button>
<button id="tozala" onClick={() => yubor({ turi: "tozalandi" })}>
Tozalash
</button>
</div>
);
}
await chiz(<Savat />);
console.log(matn("#holat"));
await bos("#qosh");
await bos("#qosh");
console.log(matn("#holat"));
await bos("#chegirma");
console.log(matn("#holat"));
await bos("#tozala");
console.log(matn("#holat"));
0 ta, jami: 0
2 ta, jami: 100000
2 ta, jami: 80000
0 ta, jami: 0
Uchta qism bor:
- Reduktor - joriy holat va amalni olib, yangi holat qaytaradi.
- Amal - nima bo'lganini tasvirlaydigan obyekt.
yubor- amalni reduktorga yuboradi.
useReducer(reduktor, boshlangich) [holat, yubor] qaytaradi -
useState ga o'xshash.
Reduktor uchta qoidaga bo'ysunadi:
- Toza - so'rov yubormaydi, taymer qo'ymaydi, tasodifiy son yaratmaydi.
- Yangi obyekt qaytaradi - eskisini o'zgartirmaydi.
- Faqat argumentlariga tayanadi - tashqi o'zgaruvchiga emas.
Shuning uchun uni React siz ham chaqirish mumkin - bu sinashni juda osonlashtiradi. Buni quyida ko'ramiz.
Nomi Array.prototype.reduce dan kelgan: u ham to'plangan
qiymat va yangi elementni olib, yangi to'plangan qiymat
qaytaradi.
Amal nomlari #
const yaxshi = [
{ turi: "vazifa_qoshildi", matn: "O'qish" },
{ turi: "vazifa_bajarildi", id: 3 },
{ turi: "filtr_ozgardi", qiymat: "hammasi" },
];
const yomon = [
{ turi: "ozgartir", maydon: "matn", qiymat: "O'qish" },
{ turi: "set_bajarildi", id: 3, qiymat: true },
];
console.log("Yaxshi nomlar - nima BO'LGANI:");
for (const a of yaxshi) console.log(" ", a.turi);
console.log("Yomon nomlar - nima QILISH kerakligi:");
for (const a of yomon) console.log(" ", a.turi);
Yaxshi nomlar - nima BO'LGANI:
vazifa_qoshildi
vazifa_bajarildi
filtr_ozgardi
Yomon nomlar - nima QILISH kerakligi:
ozgartir
set_bajarildi
Amal - bu "nima sodir bo'ldi", "nima qilish kerak" emas.
vazifa_bajarildi - foydalanuvchi vazifani bajarganini aytadi.
Reduktor esa bunga javoban nima qilishni o'zi hal qiladi:
belgini o'zgartiradi, hisoblagichni yangilaydi, filtrni
qayta ishlaydi.
set_bajarildi esa allaqachon qaror qabul qilgan - shunda
reduktor shunchaki nusxa ko'chiruvchiga aylanadi va uning
foydasi yo'qoladi.
Bu farq jurnal yozganda ham sezilarli: vazifa_bajarildi
tarixini o'qish oson, set_... esa hech narsa aytmaydi.
Reduktorni alohida sinash #
const boshlangich = { soni: 0, tarix: [] };
function reduktor(holat, amal) {
switch (amal.turi) {
case "oshdi":
return {
soni: holat.soni + 1,
tarix: [...holat.tarix, "oshdi"],
};
case "kamaydi":
return {
soni: holat.soni - 1,
tarix: [...holat.tarix, "kamaydi"],
};
case "tiklandi":
return boshlangich;
default:
throw new Error(`noma'lum amal: ${amal.turi}`);
}
}
let holat = boshlangich;
holat = reduktor(holat, { turi: "oshdi" });
holat = reduktor(holat, { turi: "oshdi" });
holat = reduktor(holat, { turi: "kamaydi" });
console.log("soni:", holat.soni);
console.log("tarix:", holat.tarix.join(" -> "));
console.log("boshlangich o'zgarmadi:", boshlangich.soni === 0);
try {
reduktor(holat, { turi: "yoq" });
} catch (x) {
console.log("xato:", x.message);
}
soni: 1
tarix: oshdi -> oshdi -> kamaydi
boshlangich o'zgarmadi: true
xato: noma'lum amal: yoq
Reduktor React siz sinaldi - hech qanday komponent, DOM yoki chizish kerak bo'lmadi.
Bu useReducer ning eng katta amaliy foydasi: murakkab mantiqni
oddiy funksiya sifatida to'liq qoplash mumkin.
useState yoki useReducer #
| Belgi | Tanlov |
|---|---|
| Bitta-ikkita oddiy qiymat | useState |
| Keyingi holat oldingisiga murakkab bog'liq | useReducer |
| Bir amal bir necha maydonni o'zgartiradi | useReducer |
| Mantiqni alohida sinash kerak | useReducer |
| Holat chuqur va ichma-ich | useReducer |
useReducer kuchli, lekin u qo'shimcha tuzilma talab qiladi:
amal turlari, switch, alohida funksiya.
Ikkita mustaqil qiymat uchun bu ortiqcha:
const [ochiq, ochiqniOzgartir] = useState(false);
Bu yetarli va o'qishli. Uni reduktorga aylantirish faqat kodni uzaytiradi.
Qoida: useState dan boshlang. Holat murakkablashib,
o'zgartirishlar bir-biriga bog'lanib ketganda useReducer ga
o'ting.
Ko'chirish oson: reduktor odatda mavjud funksiyalarni bitta
switch ga yig'ishdan iborat.
Kontekst bilan birga #
import { createContext, useContext, useReducer } from "react";
const VazifaKonteksti = createContext(null);
const boshlangich = { vazifalar: [], keyingiId: 1 };
export function reduktor(holat, amal) {
switch (amal.turi) {
case "qoshildi":
return {
vazifalar: [
...holat.vazifalar,
{
id: holat.keyingiId,
matn: amal.matn,
bajarildi: false,
},
],
keyingiId: holat.keyingiId + 1,
};
case "bajarildi":
return {
...holat,
vazifalar: holat.vazifalar.map((v) =>
v.id === amal.id ? { ...v, bajarildi: true } : v),
};
default:
throw new Error(`noma'lum amal: ${amal.turi}`);
}
}
export function VazifaTaminlovchi({ children }) {
const [holat, yubor] = useReducer(reduktor, boshlangich);
return (
<VazifaKonteksti value={{ holat, yubor }}>
{children}
</VazifaKonteksti>
);
}
export function useVazifalar() {
const qiymat = useContext(VazifaKonteksti);
if (qiymat === null) {
throw new Error("useVazifalar VazifaTaminlovchi ichida bo'lsin");
}
return qiymat;
}
import { VazifaTaminlovchi, useVazifalar } from "./vazifa-konteksti.mjs";
import { chiz, bos, matn } from "./dom-muhit.mjs";
function Qoshuvchi() {
const { yubor } = useVazifalar();
return (
<button
id="qosh"
onClick={() => yubor({ turi: "qoshildi", matn: "Yangi" })}
>
Qo'shish
</button>
);
}
function Royxat() {
const { holat, yubor } = useVazifalar();
return (
<div>
<p id="soni">
{holat.vazifalar.length} ta,
bajarilgan:{" "}
{holat.vazifalar.filter((v) => v.bajarildi).length}
</p>
<button
id="bajar"
onClick={() => yubor({ turi: "bajarildi", id: 1 })}
>
Birinchisini bajarish
</button>
</div>
);
}
await chiz(
<VazifaTaminlovchi>
<Qoshuvchi />
<Royxat />
</VazifaTaminlovchi>,
);
const holat = () => matn("#soni").replace(/\s+/g, " ").trim();
console.log(holat());
await bos("#qosh");
await bos("#qosh");
console.log(holat());
await bos("#bajar");
console.log(holat());
0 ta, bajarilgan: 0
2 ta, bajarilgan: 0
2 ta, bajarilgan: 1
useReducer va useContext birga - bu kichik va o'rta
ilovalar uchun to'liq holat boshqaruvi.
Yuqoridagi naqsh Redux ning asosiy g'oyasini takrorlaydi:
yagona holat, amallar, reduktor.
Ko'p loyiha uchun bu yetarli va qo'shimcha kutubxona kerak emas.
Maxsus kutubxona (Redux Toolkit, Zustand, Jotai) quyidagi
holatlarda foydali:
- Holat juda katta va tez-tez o'zgaradi;
- Nosozlik izlash vositalari kerak (amallar tarixi, vaqt bo'ylab orqaga qaytish);
- Server ma'lumotini keshlash kerak - bu holda
TanStack Query(11-bo'lim) to'g'riroq tanlov.
Kutubxonani muammo paydo bo'lgandan keyin qo'shing, oldindan emas.
- Uch holatli komponentni
useStatebilan yozing. - Uni
useReducerga o'tkazing. - Amal nomlarini "nima bo'lgani" tarzda yozing.
- Noma'lum amal uchun xato tashlang.
- Reduktorni React siz, to'g'ridan-to'g'ri chaqirib sinang.
- Boshlang'ich holat o'zgarmasligini tekshiring.
- Reduktorni kontekst bilan birlashtiring.
- O'z ilgagingiz orqali holatga murojaat qiling.
- Ikki komponentdan bir holatni o'zgartiring.
- Qaysi holatda
useStateyetarli ekanini ayting.
Xulosa #
useReducerhamma o'zgarishni bitta funksiyaga yig'adi.- Uchta qism: reduktor, amal,
yubor. - Reduktor - toza funksiya: yangi obyekt qaytaradi va faqat argumentlariga tayanadi.
- Amal nomi nima bo'lganini tasvirlasin, nima qilishni emas.
- Reduktorni React siz sinash mumkin - eng katta amaliy foyda.
useStatedan boshlang; murakkablashgandauseReducerga o'ting.useReducer+useContext- kichik ilovalar uchun to'liq holat boshqaruvi.
Keyingi bo'limda o'z ilgaklaringizni yozamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.