Bài 21.5: Bài tập thực hành Redux

Bài 21.5: Bài tập thực hành Redux
Chào mừng trở lại series blog về Lập trình Web Front-end! Sau khi đã cùng nhau đi qua lý thuyết và các khái niệm cốt lõi của Redux, giờ là lúc chúng ta đưa kiến thức đó vào thực tế. Không có gì hiệu quả hơn việc tự tay code để thực sự hiểu cách một thư viện hay framework hoạt động.
Trong bài thực hành này, chúng ta sẽ xây dựng một ứng dụng cực kỳ phổ biến trong thế giới Redux: một Bộ đếm số đơn giản (Counter). Nghe có vẻ đơn giản, nhưng nó sẽ bao gồm tất cả các thành phần cốt lõi của Redux và cách tích hợp chúng với React.
Hãy chuẩn bị môi trường code của bạn và cùng bắt đầu nào!
Chuẩn Bị
Đảm bảo bạn đã có một dự án React cơ bản. Nếu chưa, bạn có thể tạo nhanh bằng Create React App (dù không còn được khuyến khích cho dự án mới, nhưng đủ dùng cho bài tập này) hoặc Vite:
# Sử dụng Vite
npm create vite my-redux-app --template react
cd my-redux-app
npm install
# Cài đặt các thư viện cần thiết
npm install redux react-redux @reduxjs/toolkit
Giải thích:
redux
: Thư viện lõi của Redux.react-redux
: Cung cấp các liên kết (bindings) để Redux hoạt động dễ dàng với React (nhưProvider
,useSelector
,useDispatch
).@reduxjs/toolkit
: Bộ công cụ được khuyến nghị từ Redux để giúp viết code Redux đơn giản và hiệu quả hơn (giảm boilerplate). Chúng ta sẽ sử dụng nó để thiết lập store.
Cấu Trúc Dự Án (Gợi ý)
Để giữ cho mọi thứ có tổ chức, bạn có thể tạo cấu trúc thư mục đơn giản như sau trong thư mục src
:
src/
├── actions/
│ └── counterActions.js
├── reducers/
│ └── counterReducer.js
├── components/
│ └── Counter.js
├── store.js
└── index.js (hoặc App.js)
Bước 1: Định Nghĩa Actions
Actions là các object đơn giản mô tả điều gì đã xảy ra trong ứng dụng. Chúng có thuộc tính type
bắt buộc.
Chúng ta sẽ định nghĩa các action để tăng, giảm và đặt lại bộ đếm.
// src/actions/counterActions.js
// Định nghĩa các loại action dưới dạng hằng số để tránh lỗi chính tả
export const INCREMENT = 'counter/increment'; // Quy ước đặt tên slice/action
export const DECREMENT = 'counter/decrement';
export const RESET = 'counter/reset';
// Action Creators: Các hàm tạo ra action object
export const increment = () => ({
type: INCREMENT
});
export const decrement = () => ({
type: DECREMENT
});
export const reset = () => ({
type: RESET
});
Giải thích:
- Chúng ta sử dụng hằng số để định nghĩa
type
của action. Điều này giúp trình soạn thảo code (IDE) bắt lỗi chính tả và dễ dàng gỡ lỗi hơn. action creators
là các hàm trả về action object. Việc sử dụng chúng giúp code của bạn dễ đọc và tái sử dụng hơn khi cần dispatch action.
Bước 2: Xây Dựng Reducer
Reducer là hàm thuần khiết (pure function) nhận vào trạng thái hiện tại (state
) và action, sau đó trả về trạng thái mới. Reducer là nơi duy nhất logic thay đổi trạng thái được xử lý.
// src/reducers/counterReducer.js
import { INCREMENT, DECREMENT, RESET } from '../actions/counterActions';
// Định nghĩa trạng thái ban đầu
const initialState = {
count: 0
};
// Reducer cho bộ đếm
const counterReducer = (state = initialState, action) => {
// Sử dụng switch statement để xử lý các loại action khác nhau
switch (action.type) {
case INCREMENT:
// Trả về trạng thái mới bằng cách sao chép trạng thái cũ
// và cập nhật thuộc tính 'count'
return {
...state, // Sao chép tất cả các thuộc tính khác của state (nếu có)
count: state.count + 1
};
case DECREMENT:
// Đảm bảo count không âm (tùy chọn logic)
return {
...state,
count: state.count > 0 ? state.count - 1 : 0
};
case RESET:
return {
...state,
count: 0
};
default:
// Nếu action không khớp với bất kỳ case nào,
// luôn trả về trạng thái hiện tại mà không thay đổi
return state;
}
};
export default counterReducer;
Giải thích:
- Reducer nhận
state
(với giá trị mặc định làinitialState
khi khởi tạo) vàaction
. - Điều quan trọng nhất là reducer không được thay đổi trực tiếp trạng thái hiện tại (
state
). Thay vào đó, nó phải trả về một object trạng thái mới. Chúng ta dùng cú pháp spread (...state
) để sao chép trạng thái cũ và chỉ cập nhật những phần cần thay đổi. default
case là bắt buộc để đảm bảo reducer luôn trả về trạng thái hiện tại nếu nhận được một action mà nó không xử lý.
Bước 3: Thiết Lập Store
Store là nơi duy nhất chứa toàn bộ trạng thái của ứng dụng. Chúng ta sẽ sử dụng configureStore
từ Redux Toolkit để thiết lập nó.
// src/store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './reducers/counterReducer';
// Cấu hình store
const store = configureStore({
reducer: {
// Đăng ký reducer của bạn ở đây.
// Key 'counter' sẽ là tên của phần slice state này trong store gốc.
counter: counterReducer
// Nếu có nhiều reducers khác, bạn thêm vào đây:
// user: userReducer,
// products: productsReducer
}
});
export default store;
Giải thích:
configureStore
là hàm được đề xuất bởi Redux Toolkit để tạo store. Nó tự động kết hợp reducers, thiết lập Redux DevTools Extension và thêm một số middleware hữu ích theo mặc định.- Chúng ta truyền vào một object
reducer
, nơi mỗi key (ví dụ:'counter'
) tương ứng với một slice (lát cắt) của trạng thái gốc trong store, và value là reducer quản lý slice đó.
Bước 4: Kết Nối Redux với React App
Để các component React có thể truy cập store, chúng ta cần bọc toàn bộ ứng dụng bằng component Provider
từ react-redux
.
Mở file gốc của ứng dụng (thường là src/index.js
hoặc src/main.jsx
nếu dùng Vite):
// src/index.js (hoặc tương tự)
import React from 'react';
import ReactDOM from 'react-dom/client'; // hoặc 'react-dom'
import { Provider } from 'react-redux'; // Import Provider
import store from './store'; // Import store
import App from './App'; // Component gốc của ứng dụng
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode> {/* React.StrictMode giúp phát hiện vấn đề tiềm ẩn */}
<Provider store={store}> {/* Bọc App bằng Provider và truyền store */}
<App />
</Provider>
</React.StrictMode>
);
Giải thích:
Provider
là một component cấp cao (Higher-Order Component) từreact-redux
. Nó sử dụng React Context để đưa store Redux vào cây component, giúp mọi component con bên trong có thể truy cập store mà không cần prop drilling.- Bạn cần truyền instance
store
đã tạo ở Bước 3 vào propstore
củaProvider
.
Bước 5: Sử Dụng State và Dispatch Actions trong Component React
Cuối cùng, chúng ta sẽ tạo một component React (Counter.js
) để hiển thị giá trị của bộ đếm và các nút để tương tác với nó. Chúng ta sẽ sử dụng các React Hooks được cung cấp bởi react-redux
: useSelector
để đọc state và useDispatch
để gửi actions.
// src/components/Counter.js
import React from 'react';
// Import các hooks từ react-redux
import { useSelector, useDispatch } from 'react-redux';
// Import các action creators đã định nghĩa
import { increment, decrement, reset } from '../actions/counterActions';
function Counter() {
// Sử dụng useSelector để đọc giá trị 'count' từ slice 'counter' trong state Redux
const count = useSelector(state => state.counter.count);
// Sử dụng useDispatch để lấy hàm dispatch
const dispatch = useDispatch();
return (
<div>
<h1>Bộ đếm Redux</h1>
<p>Giá trị hiện tại: <strong>{count}</strong></p> {/* Hiển thị giá trị */}
<button onClick={() => dispatch(increment())}>Tăng (+)</button> {/* Nút Tăng */}
<button onClick={() => dispatch(decrement())}>Giảm (-)</button> {/* Nút Giảm */}
<button onClick={() => dispatch(reset())}>Đặt lại</button> {/* Nút Đặt lại */}
</div>
);
}
export default Counter;
Giải thích:
useSelector(selectorFunction)
: Hook này cho phép bạn "chọn" (select) một phần dữ liệu cụ thể từ trạng thái gốc của Redux store. HàmselectorFunction
nhận toàn bộ trạng thái gốc (state
) và trả về dữ liệu bạn muốn component sử dụng. Ở đây, chúng ta lấystate.counter.count
. Component sẽ tự động re-render khi giá trị mà selector trả về thay đổi.useDispatch()
: Hook này trả về hàmdispatch
từ Redux store. Bạn sử dụng hàmdispatch
này để gửi (fire) một action đến store.- Trong các event handler (
onClick
), chúng ta gọidispatch()
và truyền vào kết quả của việc gọi các action creators (ví dụ:increment()
). Khidispatch(increment())
được gọi, action{ type: 'counter/increment' }
sẽ được gửi đến reducer, reducer xử lý và cập nhật state,useSelector
nhận thấy state đã thay đổi và componentCounter
re-render với giá trịcount
mới.
Bước 6: Hiển Thị Component Counter
Cuối cùng, hãy thêm component Counter
vào component gốc của ứng dụng (App.js
):
// src/App.js
import React from 'react';
import Counter from './components/Counter'; // Import component Counter
function App() {
return (
<div className="App" style={{ textAlign: 'center', marginTop: '50px' }}>
{/* Các nội dung khác của App */}
<Counter /> {/* Hiển thị component Counter */}
{/* Các nội dung khác */}
</div>
);
}
export default App;
Chạy Ứng Dụng
Bây giờ, bạn có thể chạy ứng dụng React của mình:
npm start
# hoặc nếu dùng Vite
npm run dev
Mở trình duyệt tại địa chỉ tương ứng (thường là http://localhost:3000
hoặc http://localhost:5173
), bạn sẽ thấy giao diện bộ đếm đơn giản hoạt động. Khi bạn click vào các nút "Tăng" hoặc "Giảm", giá trị hiển thị sẽ thay đổi, và sự thay đổi này được quản lý hoàn toàn bởi Redux.
Comments