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 useState
dan 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. useState
ni useReducer
ning yuqori darajadagi abstraksiyasi deb hisoblash mumkin. Hatto, istasangiz, useState
ni 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 useState
ga 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 useState
ni ishlatmaymiz, axir u oddiyroq-ku?
useReducer
dan 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 useState
ni va qachon useReducer
ni 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.