Redux Toolkit — slice, reducer, action
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 ichidafetch,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
thunkmiddleware 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()—dispatchfunksiyasini qaytaradi; action yuborish uchun
[!WARNING]
useSelectorham 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 hollardacreateSelector(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 ustidauseSelector(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