Redux Toolkit — slice, reducer, action

~20 daq · React · State Boshqarish

Muammodan boshlaylik

59-darsda Redux ning g'oyasini ko'rdik. Endi amalda: savat va mavzu (qorong'i/yorug') uchun Redux Toolkit bilan global state quramiz. Kerak bo'ladigan qismlar:

  • Slice — state ning bir bo'lagi: boshlang'ich qiymat va uni o'zgartiradigan reducer lar
  • Store — barcha slice larni birlashtiradi
  • Provider — store ni React ga ulaydi
  • useSelector va useDispatch — komponentlarda o'qish va o'zgartirish

1. Slice yaratish

// store/savatSlice.js
import { createSlice } from '@reduxjs/toolkit';

const initialState = {
  mahsulotlar: [],
};

const savatSlice = createSlice({
  name: 'savat',
  initialState,
  reducers: {
    qoshish: (state, action) => {
      const bor = state.mahsulotlar.find((m) => m.id === action.payload.id);
      if (bor) {
        bor.soni += 1;
      } else {
        state.mahsulotlar.push({ ...action.payload, soni: 1 });
      }
    },
    ochirish: (state, action) => {
      state.mahsulotlar = state.mahsulotlar.filter((m) => m.id !== action.payload);
    },
    soniniOzgartir: (state, action) => {
      const { id, soni } = action.payload;
      const m = state.mahsulotlar.find((x) => x.id === id);
      if (m) m.soni = soni;
    },
    tozalash: () => initialState,
  },
});

export const { qoshish, ochirish, soniniOzgartir, tozalash } = savatSlice.actions;
export default savatSlice.reducer;

createSlice bir vaqtda uchta narsa yaratadi:

Natija Nima Misol
savatSlice.reducer Reducer funksiya — store ga ulanadi savatReducer
savatSlice.actions Action creator lar — har bir reducer uchun qoshish(m) → { type: 'savat/qoshish', payload: m }
Action type lar name + reducer nomi 'savat/qoshish'

Immer — "o'zgartirish" mumkin

qoshish: (state, action) => {
  state.mahsulotlar.push(action.payload);   // ✅ RTK ichida — xavfsiz
}

RTK ichida immer o'rnatilgan (58-dars): siz state ni "o'zgartirayotgandek" yozasiz, immer esa yangi o'zgarmas obyekt yasaydi. Bu faqat createSlice va createReducer ichida ishlaydi.

[!WARNING] Reducer da ikkala usulni aralashtirmang: yo state ni o'zgartiring (state.x = ..., hech narsa qaytarmang), yo yangi qiymat qaytaring (return initialState). Ikkalasini bir vaqtda — state.x = 1; return { ...state } — immer xato beradi. Shuningdek reducer ichida fetch, Math.random(), Date.now() kabi nosof amallar bo'lmasin — reducer sof funksiya.

action.payload

dispatch(qoshish({ id: 42, nomi: 'Kitob', narx: 50000 }));
// action = { type: 'savat/qoshish', payload: { id: 42, nomi: 'Kitob', narx: 50000 } }

dispatch(ochirish(42));
// action = { type: 'savat/ochirish', payload: 42 }

Action creator ga berilgan bitta argument — payload. Ko'p qiymat — obyekt bilan.


2. Ikkinchi slice

// store/mavzuSlice.js
import { createSlice } from '@reduxjs/toolkit';

const mavzuSlice = createSlice({
  name: 'mavzu',
  initialState: { rejim: 'yorug' },
  reducers: {
    almashtir: (state) => {
      state.rejim = state.rejim === 'yorug' ? 'qorongi' : 'yorug';
    },
  },
});

export const { almashtir } = mavzuSlice.actions;
export default mavzuSlice.reducer;

3. Store sozlash

// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import savatReducer from './savatSlice';
import mavzuReducer from './mavzuSlice';

export const store = configureStore({
  reducer: {
    savat: savatReducer,
    mavzu: mavzuReducer,
  },
});

Store ning umumiy state i:

{
  savat: { mahsulotlar: [...] },
  mavzu: { rejim: 'yorug' },
}

configureStore avtomatik ravishda:

  • Redux DevTools ni ulaydi
  • thunk middleware ni qo'shadi (61-dars)
  • Development da state ni tasodifan to'g'ridan-to'g'ri o'zgartirishni va serializatsiya qilib bo'lmaydigan qiymatlarni (funksiya, Date, Map) aniqlab ogohlantiradi

4. Provider

// main.jsx
import { Provider } from 'react-redux';
import { store } from './store';

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </StrictMode>
);

5. Komponentlarda — useSelector va useDispatch

import { useSelector, useDispatch } from 'react-redux';
import { qoshish } from '../store/savatSlice';

function MahsulotKartochka({ mahsulot }) {
  const dispatch = useDispatch();

  return (
    <article>
      <h3>{mahsulot.nomi}</h3>
      <button onClick={() => dispatch(qoshish(mahsulot))}>Savatga</button>
    </article>
  );
}

function SavatBelgisi() {
  const soni = useSelector((state) =>
    state.savat.mahsulotlar.reduce((s, m) => s + m.soni, 0)
  );
  return <span>🛒 {soni}</span>;
}
  • useSelector(selektor) — store dan qiymat oladi; qiymat o'zgarganda komponent render bo'ladi (Zustand dagi selektor kabi — 57-dars)
  • useDispatch() — dispatch funksiyasini qaytaradi; action yuborish uchun

[!WARNING] useSelector ham har chaqirilganda yangi obyekt yoki massiv qaytaradigan selektor bilan har renderda komponentni qayta render qiladi va development da ogohlantiradi: useSelector((s) => s.savat.mahsulotlar.filter(...)). Bunday hollarda createSelector (pastda) ishlating yoki selektorni oddiy qiymat qaytaradigan qilib yozing.


Selektorlarni alohida yozish

Selektorlarni slice faylida saqlash yaxshi amaliyot:

// store/savatSlice.js
export const selectMahsulotlar = (state) => state.savat.mahsulotlar;
export const selectSoni = (state) =>
  state.savat.mahsulotlar.reduce((s, m) => s + m.soni, 0);
const soni = useSelector(selectSoni);

State tuzilmasi o'zgarsa — faqat selektorni yangilaysiz, komponentlarni emas.

createSelector — memoizatsiya

import { createSelector } from '@reduxjs/toolkit';

export const selectJami = createSelector(
  [selectMahsulotlar],
  (mahsulotlar) => mahsulotlar.reduce((s, m) => s + m.narx * m.soni, 0)
);

export const selectQimmatlar = createSelector(
  [selectMahsulotlar],
  (mahsulotlar) => mahsulotlar.filter((m) => m.narx > 1_000_000)
);

createSelector natijani eslab qoladi: kirish (mahsulotlar) o'zgarmasa, hisoblash qayta bajarilmaydi va o'sha massiv qaytariladi — ortiqcha render yo'q.


To'liq oqim

1. Foydalanuvchi "Savatga" bosadi
2. dispatch(qoshish(mahsulot))
       → { type: 'savat/qoshish', payload: mahsulot }
3. Store action ni barcha reducer larga beradi
4. savatReducer 'savat/qoshish' ni taniydi → yangi state
5. useSelector lar selektorlarni qayta hisoblaydi
6. Qiymati o'zgargan komponentlar (SavatBelgisi) render bo'ladi

Redux DevTools ni ochsangiz — savat/qoshish action, uning payload i va state ning oldin/keyin holati ko'rinadi.


Typed hooks — TypeScript bilan

// store/index.ts
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

// store/hooks.ts
import { useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './index';

export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
export const useAppSelector = useSelector.withTypes<RootState>();

TypeScript loyihalarida useSelector o'rniga useAppSelector — state turi avtomatik aniqlanadi.


Papka tuzilmasi

src/
├── store/
│   └── index.js              // configureStore
└── features/
    ├── savat/
    │   ├── savatSlice.js     // slice + selektorlar
    │   ├── Savat.jsx
    │   └── SavatBelgisi.jsx
    └── mavzu/
        ├── mavzuSlice.js
        └── MavzuTugmasi.jsx

Redux jamoasi tavsiyasi — "feature folders": slice va uni ishlatadigan komponentlar bir papkada.


Nima uchun shunday?

Nima uchun createSlice action creator larni avtomatik yaratadi — klassik Redux da ularni qo'lda yozish kerak edi?

Chunki klassik Redux da bitta o'zgarish uchun uch joyda bir xil nomni takrorlash kerak edi: action type konstantasi (const ADD = 'savat/add'), action creator (const add = (p) => ({ type: ADD, payload: p })) va reducer dagi case ADD:. Uchalasi doim birga o'zgaradi, va birini unutish — jimgina ishlamaydigan kod. Redux Toolkit bu takrorlanishni ko'rdi: reducer nomi ma'lum bo'lsa, type ham, action creator ham undan mexanik ravishda kelib chiqadi. Kod uch baravar qisqardi, va "type ni noto'g'ri yozdim" degan xatolar butunlay yo'qoldi. Redux ning asosiy g'oyasi — action lar va sof reducer lar — o'zgarmadi; faqat uni yozish usuli insoniylashdi.


Xulosa

Eslab qoling:

  • createSlice({ name, initialState, reducers }) → .reducer + .actions
  • Reducer ichida immer: state ni "o'zgartirish" yoki yangi qiymat qaytarish — ikkalasini birga emas
  • Reducer — sof funksiya: fetch, random, Date.now() yo'q
  • configureStore({ reducer: { savat, mavzu } }) — DevTools va thunk avtomatik
  • <Provider store={store}> — ilova ustida
  • useSelector(selektor) — o'qish; useDispatch() + dispatch(action(payload)) — o'zgartirish
  • Selektorlar slice faylida; hisoblanganlari — createSelector
  • TypeScript: useAppSelector, useAppDispatch

Keyingi dars

61-dars: Redux Toolkit — createAsyncThunk, RTK Query