14-bo‘lim

useReducer

Murakkab holatni reduktor bilan boshqarish, amallar, useState bilan taqqoslash, kontekst bilan birlashtirish va reduktorni alohida sinash.

🕑 9 daqiqa o‘qish 📄 595 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Muammo
  2. Reduktor bilan
  3. Amal nomlari
  4. Reduktorni alohida sinash
  5. useState yoki useReducer
  6. Kontekst bilan birga
  7. Xulosa

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 #

JSX
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"));
Natija
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 #

JSX
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"));
Natija
0 ta, jami: 0
2 ta, jami: 100000
2 ta, jami: 80000
0 ta, jami: 0

Uchta qism bor:

  1. Reduktor - joriy holat va amalni olib, yangi holat qaytaradi.
  2. Amal - nima bo'lganini tasvirlaydigan obyekt.
  3. yubor - amalni reduktorga yuboradi.

useReducer(reduktor, boshlangich) [holat, yubor] qaytaradi - useState ga o'xshash.

Reduktor - toza funksiya

Reduktor uchta qoidaga bo'ysunadi:

  1. Toza - so'rov yubormaydi, taymer qo'ymaydi, tasodifiy son yaratmaydi.
  2. Yangi obyekt qaytaradi - eskisini o'zgartirmaydi.
  3. 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 #

JSX
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);
Natija
Yaxshi nomlar - nima BO'LGANI:
  vazifa_qoshildi
  vazifa_bajarildi
  filtr_ozgardi
Yomon nomlar - nima QILISH kerakligi:
  ozgartir
  set_bajarildi
Amal nomi hodisani tasvirlasin

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 #

JSX
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);
}
Natija
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 #

BelgiTanlov
Bitta-ikkita oddiy qiymatuseState
Keyingi holat oldingisiga murakkab bog'liquseReducer
Bir amal bir necha maydonni o'zgartiradiuseReducer
Mantiqni alohida sinash kerakuseReducer
Holat chuqur va ichma-ichuseReducer
useReducer - har doim yaxshiroq emas

useReducer kuchli, lekin u qo'shimcha tuzilma talab qiladi: amal turlari, switch, alohida funksiya.

Ikkita mustaqil qiymat uchun bu ortiqcha:

JSX
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 #

JSX
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;
}
JSX
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());
Natija
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.

Redux qachon kerak

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.

Amaliy topshiriq
  1. Uch holatli komponentni useState bilan yozing.
  2. Uni useReducer ga o'tkazing.
  3. Amal nomlarini "nima bo'lgani" tarzda yozing.
  4. Noma'lum amal uchun xato tashlang.
  5. Reduktorni React siz, to'g'ridan-to'g'ri chaqirib sinang.
  6. Boshlang'ich holat o'zgarmasligini tekshiring.
  7. Reduktorni kontekst bilan birlashtiring.
  8. O'z ilgagingiz orqali holatga murojaat qiling.
  9. Ikki komponentdan bir holatni o'zgartiring.
  10. Qaysi holatda useState yetarli ekanini ayting.

Xulosa #

  • useReducer hamma 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.
  • useState dan boshlang; murakkablashganda useReducer ga o'ting.
  • useReducer + useContext - kichik ilovalar uchun to'liq holat boshqaruvi.

Keyingi bo'limda o'z ilgaklaringizni yozamiz.

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.