useState va useReducer

React state’ni boshqarish uchun mo’ljallangan ikkita hook’larni taklif etadi: useState va useReducer. Ikkalasi ham komponentda state’ni boshqarish uchun ishlatiladi. Farqi shundaki, useState birgina state’ni boshqarishga yaxshiroq mos kelsa, useReducer murakkabroq state’ni boshqarish uchun ishlatiladi.

Keling, useState bilan komponentda state’ni boshqarish usulini ko’rib chiqaylik:

import { useState } from "react";
 
const MyComponent = () => {
    const [count, setCount] = useState(0);
 
    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={() => setCount(count + 1)}>Increment</button>
        </div>
    );
};

Ushbu misolda, biz useStatedan foydalanib bitta state’ni boshqaryapmiz: count. Ammo, agar bizning state’imiz biroz murakkabroq bo’lsa-chi?

import { useState } from "react";
 
const MyComponent = () => {
    const [state, setState] = useState({
        count: 0,
        name: "Tejumma",
        age: 30,
    });
 
    return (
        <div>
            <p>Count: {state.count}</p>
            <p>Name: {state.name}</p>
            <p>Age: {state.age}</p>
            <button onClick={() => setState({ ...state, count: state.count + 1 })}>
                Increment
            </button>
        </div>
    );
};

Endi ko’rib turibmizki, bizdagi state’lar biroz murakkabroq. Bizda count, name, va age mavjud. Tugmani bosish orqali hisobni oshiramiz, bu esa state’ni eski qiymatlar bilan bir xil bo’lgan yangi obyektga o’rnatadi, faqat count birga oshirilgan bo’ladi. Bu React’da juda keng tarqalgan amaliyot. Ammo, bu usulda xatolik yuzaga kelishi mumkin, masalan, agar eski state’ni diqqat bilan “spread” qilmasak, ba’zi xususiyatlar noto’g’ri o’zgartirilishi mumkin.

useState ham useReducer’dan foydalanadi

useState ichida aslida useReducer ishlatiladi. useStateni useReducerning yuqori darajadagi abstraksiyasi deb hisoblash mumkin. Hatto, istasangiz, useStateni useReducer yordamida qayta yaratishingiz mumkin:

import { useReducer } from "react";
 
function useState(initialState) {
    const [state, dispatch] = useReducer(
        (state, newValue) => newValue,
        initialState
    );
 
    return [state, dispatch];
}

Keling, shu misolni useReducer yordamida qanday amalga oshirishni ko’rib chiqaylik:

import { useReducer } from "react";
 
const initialState = {
    count: 0,
    name: "Tejumma",
    age: 30,
};
 
const reducer = (state, action) => {
    switch (action.type) {
        case "increment":
            return { ...state, count: state.count + 1 };
        default:
            return state;
    }
};
 
const MyComponent = () => {
    const [state, dispatch] = useReducer(reducer, initialState);
 
    return (
        <div>
            <p>Count: {state.count}</p>
            <p>Name: {state.name}</p>
            <p>Age: {state.age}</p>
            <button onClick={() => dispatch({ type: "increment" })}>Increment</button>
        </div>
    );
};

Ba’zilar bu usulda kod useStatega qaraganda bir oz ortiqcha cho’zilib ketishini aytadilar va ko’pchilik bunga qo’shiladi, lekin bu abstraksiyalarni pastroq darajada ishlatishda odatiy hol: abstraksiya qanchalik past bo’lsa, kod shunchalik batafsilroq bo’ladi. Abstraksiyalar odatda murakkab logikani oddiyroq sintaksis bilan almashtirish uchun mo’ljallangan, bular ya’na syntactic sugar ham deb ataladi.

Nima uchun useReducer ishlatish foydali?

Tabiiyki bir savol tug’ilishi mumkin, xo’sh, agar biz useState yordamida useReducer bilan bir xil narsani qiloladigan bo’lsak, nima uchun har doim useStateni ishlatmaymiz, axir u oddiyroq-ku?

useReducerdan foydalanishning uchta katta afzalligi mavjud:

1. Logikani komponentdan ajratadi

U yangilanish holati logika qismnini komponentdan ajratadi. Uning qo’shimcha qilib berilgan reducer funksiyasi alohida holatda test qilinishi mumkin va uni boshqa komponentlarda qayta ishlatish mumkin. Bu bizning komponentlarimizni toza va oddiy saqlash hamda yagona ma’suliyat tamoyili(single responsibility principle)ni qo’llashning ajoyib usulidir

Biz reducer funksiyasini quyidagicha test qilishimiz mumkin:

describe("reducer", () => {
    test("increment harakati berilganda count qiymatini oshirishi kerak", () => {
        const initialState = {
            count: 0,
            name: "Tejumma",
            age: 30,
        };
        const action = { type: "increment" };
        const expectedState = {
            count: 1,
            name: "Tejumma",
            age: 30,
        };
        const actualState = reducer(initialState, action);
        expect(actualState).toEqual(expectedState);
    });
 
    test("noma'lum harakat berilganda bir xil obyektni qaytarishi kerak", () => {
        const initialState = {
            count: 0,
            name: "Tejumma",
            age: 30,
        };
        const action = { type: "unknown" };
        const expectedState = initialState;
        const actualState = reducer(initialState, action);
        expect(actualState).toBe(expectedState);
    });
});

Ushbu misolda biz ikki ssenariy holatida test qilmoqdamiz: biri increment harakati reducer’ga uzatilganda, ikkinchisi esa noma’lum harakat yuborilganda.

Birinchi testda biz count qiymati 0 bo’lgan boshlang’ich state obyektini va increment harakati obyektini yaratmoqdamiz. Shundan keyin biz count qiymatining natijaviy state’da 1 ga oshirilishini kutmoqdamiz. Buning uchun toEqual matcher(ya’ni moslashtiruvchi)’idan foydalanib, kutilayotgan va asl state obyektlarini taqqoslaymiz.

Ikkinchi testda esa count qiymati 0 bo’lgan boshlang’ich state obyektini va noma’lum harakat obyektini yaratmoqdamiz. Keyin biz natijaviy state’ning boshlang’ich state obyektiga teng bo’lishini kutmoqdamiz. Buning uchun toBe matcher’idan foydalanib, kutilayotgan va asl state obyektlarini taqqoslaymiz, chunki biz havolali tenglikni test qilyapmiz.

Reducer funksiyasini shu tarzda test qilish orqali biz uning to’g’ri ishlashini va turli kirish holatlarida kutilgan natijani ishlab chiqarishini ta’minlashimiz mumkin.

2. State va uning o’zgarishlari aniq ko’rinadi

useReducer bilan bizning state va uning qanday o’zgarishi doim aniq ko’rinib turadi, va ba’zilar useState JSX daraxti qatlamlari orqali komponentning umumiy state’ini yangilash oqimini noaniq qilib qo’yishi mumkinligini ta’kidlashadi.

3. Event’ga asoslangan modelni yaratadi

useReducer bu voqealarga asoslangan (event sourced) model bo’lib, bizning dasturimizda sodir bo’lgan event’larni modellashtirish uchun ishlatilishi mumkin, bu event’larni audit log turidagi ro’yxatga yozib borishimiz mumkin. Ushbu audit log dasturimizdagi event’larni qayta ijro etish, xatolarni takrorlash yoki vaqt bo’yicha orqaga siljish orqali xatolarni tuzatish uchun ishlatilishi mumkin. Bundan tashqari, kuchli pattern’lar, masalan, bekor qilish/qaytarish, optimistik yangilanishlar va interfeysimizdagi umumiy foydalanuvchi harakatlarini kuzatish kabi imkoniyatlarni taqdim etadi.

Qachon qay birini ishlatgan ma’qul

useReducer qulay vosita bo’lsa-da, uni har doim ishlatish shart emas. Aslida, ko’pincha uni ishlatish ortiqcha bo’lishi mumkin. Shunday ekan, qachon useStateni va qachon useReducerni ishlatish kerak? Javob sizning state’ingizning murakkabligiga bog’liq. Lekin umid qilamizki, barcha ushbu ma’lumotlar bilan siz qaysi biri sizning dasturingizda ishlatilishi kerakligi haqida ko’proq tushunchaga ega bo’lasiz.