useReducer — nima, useState dan farqi, qachon ishlatiladi

~20 daq · React · Hooks — Ilg'or

Muammo: useState murakkablashib ketganda

Vazifalar ilovasini yozyapsiz. Qo'shish, o'chirish, bajarildi belgilash, hammasini tozalash — to'rtta amal. useState bilan:

function VazifaIlovasi() {
  const [vazifalar, setVazifalar] = useState([]);

  function qoshish(matn) {
    setVazifalar([...vazifalar, { id: Date.now(), matn, bajarildi: false }]);
  }

  function ochirish(id) {
    setVazifalar(vazifalar.filter(v => v.id !== id));
  }

  function toggle(id) {
    setVazifalar(vazifalar.map(v =>
      v.id === id ? { ...v, bajarildi: !v.bajarildi } : v
    ));
  }

  function hammasiniTozala() {
    setVazifalar([]);
  }
}

Ishlaydi. Lekin muammo bor: state ni qanday o'zgartirish kerakligi — qoshish, ochirish, toggle, hammasiniTozala — komponent ichida tarqalib yotibdi. Komponent o'sib borsa, bu mantiqni topish va o'zgartirish qiyinlashadi.

useReducer — bu mantiqni alohida joyga chiqaradi. Komponent faqat "qaysi amal bajarildi" deydi — qanday o'zgarishini boshqa joy hal qiladi.


useReducer qanday ishlaydi?

useState da siz yangi qiymatni to'g'ridan-to'g'ri berasiz:

setVazifalar([...vazifalar, yangiVazifa]);

useReducer da siz faqat "nima bo'ldi" ni aytasiz:

dispatch({ type: 'QOSHISH', payload: yangiVazifa });

"Qanday o'zgarish" ni esa reducer funksiyasi hal qiladi. Bu funksiya alohida yoziladi.

dispatch({ type: 'QOSHISH' })
         │
         ▼
reducer(hozirgiState, action)  ← bu funksiya hal qiladi
         │
         ▼
yangiState  →  komponent qayta chiziladi

Amaliy misol

Avval reducer funksiyasini yozamiz — bu komponentdan tashqarida:

function vazifaReducer(state, action) {
  switch (action.type) {

    case 'QOSHISH':
      // yangi vazifa qo'shish
      return [...state, { id: Date.now(), matn: action.payload, bajarildi: false }];

    case 'OCHIRISH':
      // id bo'yicha o'chirish
      return state.filter(v => v.id !== action.payload);

    case 'TOGGLE':
      // bajarildi holatini o'zgartirish
      return state.map(v =>
        v.id === action.payload ? { ...v, bajarildi: !v.bajarildi } : v
      );

    case 'TOZALA':
      return [];

    default:
      return state; // noma'lum action — o'zgartirmay qaytarish
  }
}

Endi komponentda ishlatamiz:

function VazifaIlovasi() {
  const [vazifalar, dispatch] = useReducer(vazifaReducer, []);
  const [yangiMatn, setYangiMatn] = useState('');

  return (
    <div>
      <input
        value={yangiMatn}
        onChange={e => setYangiMatn(e.target.value)}
        placeholder="Yangi vazifa..."
      />

      <button onClick={() => {
        dispatch({ type: 'QOSHISH', payload: yangiMatn });
        setYangiMatn('');
      }}>
        Qo'shish
      </button>

      <button onClick={() => dispatch({ type: 'TOZALA' })}>
        Hammasini tozala
      </button>

      <ul>
        {vazifalar.map(v => (
          <li key={v.id}>
            <input
              type="checkbox"
              checked={v.bajarildi}
              onChange={() => dispatch({ type: 'TOGGLE', payload: v.id })}
            />
            <span>{v.matn}</span>
            <button onClick={() => dispatch({ type: 'OCHIRISH', payload: v.id })}>
              ✕
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

Komponent endi faqat "qaysi tugma bosildi" ni aytadi. Qanday o'zgarishini vazifaReducer hal qiladi. Ikkalasi alohida — ikkalasini alohida o'qish, alohida o'zgartirish mumkin.


useState vs useReducer — qachon qaysi?

useState:
  ✅ Oddiy qiymat — son, string, boolean
  ✅ 1-2 ta bog'liq bo'lmagan o'zgarish
  ✅ Kichik komponent

useReducer:
  ✅ Bir-biriga bog'liq bir nechta o'zgarish
  ✅ Mantiq murakkab — ko'p shartlar
  ✅ Mantiqni komponentdan ajratish kerak
  ✅ Reducer ni alohida test qilish kerak

Oddiy qoida: agar setXxx ni chaqirishdan oldin "avval holatni tekshirish, keyin boshqa state ni ham o'zgartirish" kerak bo'lsa — useReducer vaqti keldi.


Reducer — sof funksiya

Reducer haqida bitta muhim qoida: u sof funksiya bo'lishi kerak.

Sof funksiya degani:

  • Bir xil kirish → har doim bir xil chiqish
  • Tashqariga ta'sir qilmaydi (API chaqirmaydi, DOM o'zgartirmaydi)
  • state ni to'g'ridan-to'g'ri o'zgartirmaydi
// ❌ Xato — state ni to'g'ridan-to'g'ri o'zgartirdi
function reducer(state, action) {
  state.push(action.payload); // mutatsiya!
  return state; // xuddi shu ob'ekt — React farq ko'rmaydi
}

// ✅ To'g'ri — yangi massiv qaytaradi
function reducer(state, action) {
  return [...state, action.payload];
}

Nima uchun? React state ni reference bo'yicha solishtiradi. Eski ob'ektni o'zgartirsangiz — React "hech narsa o'zgarmadi" deb o'ylaydi, ekran yangilanmaydi.


Xulosa

useReducer — murakkab state uchun useState ning kuchliroq varianti.

useState:   setQiymat(yangi)       → oddiy
useReducer: dispatch({ type })     → murakkab, tartibli

Reducer:    (state, action) → yangiState
            sof funksiya, alohida yoziladi

Komponent faqat "nima bo'ldi" ni aytadi. "Qanday o'zgarish" ni reducer hal qiladi. Bu ikki narsani ajratish — katta loyihalarda juda muhim.


Keyingi dars

20-dars: useReducer — action, dispatch, reducer funksiya