Vấn đề Redux giải quyết
Khi ứng dụng React phình to, việc truyền state qua nhiều cấp component trở nên cồng kềnh. Nhiều component ở các nhánh khác nhau cùng cần một dữ liệu, nhưng state chỉ có thể đi từ trên xuống qua props:
App
├── Header
├── Sidebar
└── Main
└── Dashboard
Nếu cả Header, Sidebar và Dashboard đều cần thông tin tài khoản, ta phải đưa state lên tầng App và truyền xuống từng tầng. Đây chính là "prop drilling". Redux giải quyết bằng cách tập trung state vào một Store chung, component nào cũng truy cập được trực tiếp.
Luồng hoạt động cơ bản
Chuỗi xử lý trong Redux gồm 4 bước:
View phát ra Action
↓
Reducer xử lý Action
↓
Store cập nhật state
↓
View render lại
| Thành phần | Vai trò |
|---|---|
| Action | Object mô tả sự kiện vừa xảy ra |
| Reducer | Hàm thuần trả về state mới dựa trên action |
| Store | Nơi lưu duy nhất toàn bộ state toàn cục |
Viết mã hiện đại với Redux Toolkit
Thay vì định nghĩa action type, action creator và reducer riêng lẻ, Redux Toolkit gói chúng lại trong createSlice:
import { createSlice } from '@reduxjs/toolkit'
const scoreSlice = createSlice({
name: 'score',
initialState: { points: 0 },
reducers: {
increase: state => { state.points += 1 },
decrease: state => { state.points -= 1 },
addBonus: (state, action) => { state.points += action.payload },
}
})
export const { increase, decrease, addBonus } = scoreSlice.actions
export default scoreSlice.reducer
Lợi ích chính:
- Tự động tạo action type và action creator.
- Tích hợp Immer, cho phép viết mutate trực tiếp nhưng vẫn đảm bảo immutability.
- Cắt giảm đáng kể boilerplate so với cách viết truyền thống.
Kết nối với React
import { useSelector, useDispatch } from 'react-redux'
import { increase, addBonus } from './scoreSlice'
function ScoreBoard() {
const points = useSelector(state => state.score.points)
const dispatch = useDispatch()
return (
<div>
<p>{points}</p>
<button onClick={() => dispatch(increase())}>+1</button>
<button onClick={() => dispatch(addBonus(10))}>+10</button>
</div>
)
}
Xử lý tác vụ bất đng bộ
createAsyncThunk giúp quản lý các thao tác API mà không cần tự viết action cho từng trạng thái:
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
export const loadProfile = createAsyncThunk(
'profile/load',
async (profileId) => {
const res = await fetch(`/api/profiles/${profileId}`)
return res.json()
}
)
const profileSlice = createSlice({
name: 'profile',
initialState: { info: null, isLoading: false, error: null },
reducers: {},
extraReducers: builder => {
builder
.addCase(loadProfile.pending, state => {
state.isLoading = true
})
.addCase(loadProfile.fulfilled, (state, action) => {
state.isLoading = false
state.info = action.payload
})
.addCase(loadProfile.rejected, (state, action) => {
state.isLoading = false
state.error = action.error.message
})
}
})
Mẹo 1: Tách selector ra khỏi component
// Không nên: logic nặng trong component
const total = useSelector(state =>
state.basket.goods.reduce((sum, i) => sum + i.cost * i.amount, 0)
)
// Nên: đưa vào slice, tái sử dụng
export const selectBasketTotal = state =>
state.basket.goods.reduce((sum, i) => sum + i.cost * i.amount, 0)
// Trong component
const total = useSelector(selectBasketTotal)
Mẹo 2: Memo hóa với createSelector
import { createSelector } from '@reduxjs/toolkit'
const selectGoods = state => state.basket.goods
export const selectBasketTotal = createSelector(
[selectGoods],
goods => goods.reduce((sum, i) => sum + i.cost * i.amount, 0)
)
Mẹo 3: Chia slice theo chức năng
// store/index.js
import { configureStore } from '@reduxjs/toolkit'
import accountReducer from './accountSlice'
import basketReducer from './basketSlice'
export const store = configureStore({
reducer: {
account: accountReducer,
basket: basketReducer,
}
})
Mẹo 4: Ch đưa state toàn cục thực sự vào Redux
| Loại state | Nơi lưu |
|---|---|
| Dùng chung nhiều nơi | Redux |
| Chỉ component đó dùng | useState |
| Dữ liệu t server | SWR / TanStack Query |
| Form | React Hook Form |
| State trên URL | URL params |
Khi nào không cần Redux
- Dự án nhỏ:
useState+useContextđủ dùng. - Dữ liệu server: dùng thư viện chuyên trị như TanStack Query.
- Form phức tạp: dùng React Hook Form.
Redux phát huy khi ứng dụng lớn cần state chia sẻ rõ ràng giữa nhiều nhánh component, ví dụ thông tin đăng nhập, quyền truy cập, giỏ hàng, giao diện chủ đề.