Trong bài viết này, tôi sẽ chỉ cho bạn cách dùng Redux Toolkit để quản lý trạng thái trong ứng dụng React. Chúng ta sẽ tập trung vào việc xây dựng cấu trúc thư mục chuẩn cho Redux Toolkit và lý do tại sao điều này lại quan trọng. Ngoài ra, tôi sẽ cung cấp một số đoạn mã ví dụ để minh họa cách thực hiện.
Nếu bạn muốn tìm hiểu cách sử dụng Redux Toolkit một cách hiệu quả và dễ bảo trì trong dự án của mình, bài viết này sẽ cung cấp cho bạn những thông tin hữu ích.
1. Tại sao nên sử dụng Bộ công cụ Redux?
Dễ dàng thiết lập store là một lợi ích lớn khi sử dụng Redux Toolkit, vì nó cung cấp các công cụ giúp việc này trở nên nhanh chóng và đơn giản. Bạn sẽ không cần phải viết lại các đoạn mã lặp đi lặp lại khi sử dụng Redux Toolkit.
Ngoài ra, Redux Toolkit tích hợp sẵn với các thư viện phổ biến như Immer js, Redux, Redux Thunk, Reselect và Redux DevTools Extension, giúp việc xây dựng ứng dụng với Redux trở nên thuận tiện và hiệu quả hơn.
Redux Toolkit cũng giúp giảm thiểu các đoạn mã lặp đi lặp lại, cho phép bạn tập trung vào việc phát triển tính năng của ứng dụng mà không phải tốn nhiều thời gian viết mã trùng lặp.
Thêm vào đó, Redux Toolkit tích hợp Redux DevTools Extension, giúp bạn dễ dàng gỡ lỗi ứng dụng Redux của mình.
2. Cấu trúc thư mục chuẩn của Redux Toolkit
Redux Toolkit là một thư viện giúp đơn giản hóa việc sử dụng Redux trong ứng dụng React. Khi xây dựng ứng dụng sử dụng Redux Toolkit, việc sắp xếp thư mục và các file cần được quan tâm để giữ cho mã nguồn dễ đọc, dễ bảo trì và dễ mở rộng.
Dưới đây là cấu trúc thư mục chuẩn được đề xuất bởi Redux Toolkit:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
src/ ├── app/ │ ├── App.js │ ├── store.js │ └── rootReducer.js ├── features/ │ ├── feature 1 / │ │ ├── feature 1 Slice.js │ │ ├── feature 1 API.js │ │ ├── feature 1 Page.js │ │ ├── feature 1 Form.js │ │ └── feature 1 List.js │ ├── feature 2 / │ │ ├── feature 2 Slice.js │ │ ├── feature 2 API.js │ │ ├── feature 2 Page.js │ │ ├── feature 2 Form.js │ │ └── feature 2 List.js │ └── ... ├── components/ │ ├── Header.js │ ├── Footer.js │ └── ... ├── styles/ │ ├── theme.js │ ├── globalStyles.js │ └── ... └── index.js // <a href= "https://freetuts.net" target= "_blank" title= "freetuts net" >https://freetuts.net</a> |
Các thư mục và tệp tin trong cấu trúc thư mục chuẩn của Redux Toolkit có các vai trò và mục đích như sau:
-`app/`: Thư mục này chứa tệp tin liên quan đến việc khởi tạo store Redux, root reducer và component chính của ứng dụng (App.js).
+`App.js`: Component chính của ứng dụng.
+`store.js`: Tệp tin khởi tạo Redux store.
+`rootReducer.js`: Tệp tin chứa root reducer của ứng dụng.
-`features/`: Thư mục này chứa các tính năng (features) của ứng dụng. Mỗi tính năng có thể bao gồm các tệp tin sau:
+`featureSlice.js`: Tệp tin chứa slice của tính năng đó.
+`featureAPI.js`: Tệp tin chứa các hàm API để giao tiếp với backend (nếu có).
+`featurePage.js`: Component chứa UI để hiển thị tính năng.
+`featureForm.js`: Component chứa form để thêm mới hoặc cập nhật dữ liệu của tính năng.
+`featureList.js`: Component chứa danh sách dữ liệu của tính năng.
-`components/`: Thư mục này chứa các components được sử dụng chung trong ứng dụng.
-`styles/`: Thư mục này chứa các tệp tin liên quan đến CSS của ứng dụng, bao gồm cả tệp tin CSS chung cho toàn bộ ứng dụng và các tệp tin CSS riêng cho từng component.
-`components/`: Thư mục này chứa các component của ứng dụng, mỗi component được đặt trong một thư mục riêng. Mỗi thư mục component bao gồm ít nhất hai tệp tin là:
+`index.js`: được sử dụng để export component tương ứng.
+`componentName.js`: chứa mã nguồn của component
3. Thiết lập Create-React-App Với Redux
Create-React-App là một công cụ tạo ứng dụng React nhanh chóng và dễ dàng, tuy nhiên, nó không bao gồm Redux. Trong bài viết này, chúng ta sẽ tìm hiểu cách cài đặt Redux cho một ứng dụng Create-React-App.
Tạo ứng dụng Create-React-App
Để bắt đầu, hãy tạo một ứng dụng Create-React-App bằng cách chạy lệnh sau trong terminal:
npx create-react-app my-ap
Chúng ta sẽ sử dụng tên “my-app” trong ví dụ này, bạn có thể đổi tên nếu muốn.
Sau khi quá trình cài đặt hoàn tất, hãy mở thư mục của ứng dụng bằng lệnh sau:
cd my-ap
Cài đặt Redux và React-Redux
Tiếp theo, chúng ta cần cài đặt Redux và React-Redux bằng lệnh sau:
npm install --save redux react-redu
Tạo store
Chúng ta sẽ tạo file store.js trong thư mục src để định nghĩa Redux store. File này sẽ export ra store object.
1
2
3
4
5
6
|
import { createStore } from 'redux' ; import rootReducer from './reducers' ; const store = createStore(rootReducer); // <a href="https://freetuts.net" target="_blank" title="freetuts net">https://freetuts.net</a> export default store; |
Trong đó, createStore là hàm để tạo store của Redux, rootReducer là reducer chính của ứng dụng.
Sử dụng useSelector() hook của React Redux
Để lấy dữ liệu từ store và sử dụng useDispatch()
hook để dispatch các action tương ứng.
Ví dụ:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
import React from 'react' ; import { useSelector, useDispatch } from 'react-redux' ; import { addUser, deleteUser, updateUser } from './usersSlice' ; function UserList() { const users = useSelector(state => state.users); const dispatch = useDispatch(); const handleAddUser = () => { const newUser = { id: 1, name: 'John Doe' }; dispatch(addUser(newUser)); }; const handleDeleteUser = id => { dispatch(deleteUser(id)); }; const handleUpdateUser = (id, updatedUser) => { dispatch(updateUser({ id, ...updatedUser })); }; return ( <div> <button onClick={handleAddUser}>Add User</button> {users.map(user => ( <div key={user.id}> <span>{user.name}</span> <button onClick={() => handleDeleteUser(user.id)}>Delete</button> <button onClick={() => handleUpdateUser(user.id, { name: 'New Name' })}>Update</button> </div> ))} </div> ); } export default UserList; |
Đó là những bước cơ bản để xây dựng một ứng dụng CRUD với Redux Toolkit. Bạn có thể tham khảo thêm tài liệu và ví dụ tại trang chủ của Redux Toolkit để biết thêm chi tiết.
Tạo reducer
Chúng ta sẽ tạo reducer chính của ứng dụng trong file src/reducers/index.js. Trong ví dụ này, chúng ta sẽ tạo một reducer đơn giản để lưu trữ giá trị số lần nhấn nút.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
const initialState = { count: 0, }; function rootReducer(state = initialState, action) { switch (action.type) { case 'INCREMENT' : return { ...state, count: state.count + 1 }; case 'DECREMENT' : return { ...state, count: state.count - 1 }; default : return state; } } // <a href="https://freetuts.net" target="_blank" title="freetuts net">https://freetuts.net</a> export default rootReducer; |
Kết nối store với ứng dụng
Trong file src/index.js, chúng ta sẽ import store và wrap ứng dụng bằng provider để cung cấp store cho toàn bộ ứng dụng.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import React from 'react' ; import ReactDOM from 'react-dom' ; import { Provider } from 'react-redux' ; import store from './store' ; import App from './App' ; ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById( 'root' ) // <a href="https://freetuts.net" target="_blank" title="freetuts net">https://freetuts.net</a> ); |
Sử dụng store trong ứng dụng React
Sau khi đã khởi tạo store, bạn có thể sử dụng nó trong ứng dụng React bằng cách sử dụng Provider component của Redux. Provider component sẽ giúp các component con trong ứng dụng có thể truy cập đến store.
Trong file index.js, import Provider và wrap App component bằng Provider component:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import React from 'react' ; import ReactDOM from 'react-dom' ; import { Provider } from 'react-redux' ; import App from './App' ; import store from './store' ; ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById( 'root' ) // <a href="https://freetuts.net" target="_blank" title="freetuts net">https://freetuts.net</a> // ); |
Sau đó, trong các component con của App, bạn có thể truy cập đến store bằng cách sử dụng hooks useSelector và useDispatch của Redux.
Ví dụ, trong component Counter, để truy cập đến state của counter trong store, bạn có thể sử dụng useSelector như sau:
1
2
3
4
5
6
7
8
9
10
11
12
|
import { useSelector } from 'react-redux' ; function Counter() { const counter = useSelector(state => state.counter); return ( <div> <h1>Counter: {counter}</h1> </div> ); // <a href="https://freetuts.net" target="_blank" title="freetuts net">https://freetuts.net</a> // } |
Để dispatch một action đến store, bạn có thể sử dụng useDispatch như sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import { useDispatch } from 'react-redux' ; import { increment, decrement } from './counterSlice' ; function Counter() { const counter = useSelector(state => state.counter); const dispatch = useDispatch(); return ( <div> <h1>Counter: {counter}</h1> <button onClick={() => dispatch(increment())}>+</button> <button onClick={() => dispatch(decrement())}>-</button> </div> ); // <a href="https://freetuts.net" target="_blank" title="freetuts net">https://freetuts.net</a> // } |
Trong đoạn code trên, khi người dùng click vào button + hoặc – thì một action tương ứng sẽ được dispatch đến store. Các component khác trong ứng dụng cũng có thể truy cập đến store và dispatch action tương tự như vậy.
Đó là cách sử dụng store của Redux trong ứng dụng React đã được tạo bằng Create React App.
4. Tạo Slice Reducer và Actions cho Redux Toolkit
Redux Toolkit cung cấp một cách tiếp cận mới cho việc tạo reducer và actions, được gọi là “Slice”. Slice là một khái niệm quan trọng trong Redux Toolkit và cung cấp một cách để định nghĩa reducer và actions của một phần của state. Trong phần này, chúng ta sẽ tạo một slice để quản lý một danh sách các task.
Tạo file Slice
Tạo một file mới trong thư mục “features” và đặt tên là taskSlice.js. File này sẽ chứa reducer và actions của slice.
Sau đó, chúng ta cần import createSlice từ Redux Toolkit:
1
|
import { createSlice } from '@reduxjs/toolkit' ; |
Khởi tạo Slice
Tiếp theo, chúng ta sẽ sử dụng createSlice để khởi tạo slice:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
const taskSlice = createSlice({ name: 'tasks' , initialState: [], reducers: { addTask: (state, action) => { const newTask = { id: Date.now(), text: action.payload, completed: false }; state.push(newTask); }, toggleTask: (state, action) => { const task = state.find(task => task.id === action.payload); if (task) { task.completed = !task.completed; } }, deleteTask: (state, action) => { const index = state.findIndex(task => task.id === action.payload); if (index !== -1) { state.splice(index, 1); } } } // <a href="https://freetuts.net" target="_blank" title="freetuts net">https://freetuts.net</a> // }); |
Ở đây, chúng ta định nghĩa một slice có tên “tasks” và khởi tạo state ban đầu là một mảng rỗng. Sau đó, chúng ta định nghĩa các reducers cho slice bằng cách sử dụng cú pháp:
1
2
3
4
5
|
reducerName: (state, action) => { // update state here // <a href="https://freetuts.net" target="_blank" title="freetuts net">https://freetuts.net</a> // } |
Ví dụ, reducer “addTask” sẽ thêm một task mới vào state bằng cách tạo một task mới với nội dung được truyền vào từ action payload và đưa nó vào mảng state.
Export reducer và actions
Cuối cùng, chúng ta sẽ xuất ra reducer và các actions của slice:
1
2
3
|
export const { addTask, toggleTask, deleteTask } = taskSlice.actions; export default taskSlice.reducer; // <a href="https://freetuts.net/" target="_blank" title="no title">https://freetuts.net</a> // |
Như vậy, chúng ta đã tạo ra một slice với reducer và các actions tương ứng cho việc quản lý một danh sách các task. Bằng cách sử dụng Slice, chúng ta đã giảm thiểu được việc viết boilerplate code và tăng tính sáng tạo cho việc quản lý state trong Redux Toolkit.
Tóm lại, việc tạo ra một cấu trúc thư mục chuẩn khi sử dụng Redux Toolkit là rất quan trọng để mã nguồn dễ đọc và dễ bảo trì. Bạn có thể tổ chức dự án của mình một cách rõ ràng và hiệu quả bằng cách tuân thủ các quy ước và hướng dẫn đơn giản.
Các thư mục và tệp tin trong cấu trúc thư mục chuẩn của Redux Toolkit có các vai trò và mục đích như sau:
-`app/`: Thư mục này chứa tệp tin liên quan đến việc khởi tạo store Redux, root reducer và component chính của ứng dụng (App.js).
+`App.js`: Component chính của ứng dụng.
+`store.js`: Tệp tin khởi tạo Redux store.
+`rootReducer.js`: Tệp tin chứa root reducer của ứng dụng.
-`features/`: Thư mục này chứa các tính năng (features) của ứng dụng. Mỗi tính năng có thể bao gồm các tệp tin sau:
+`featureSlice.js`: Tệp tin chứa slice của tính năng đó.
+`featureAPI.js`: Tệp tin chứa các hàm API để giao tiếp với backend (nếu có).
+`featurePage.js`: Component chứa UI để hiển thị tính năng.
+`featureForm.js`: Component chứa form để thêm mới hoặc cập nhật dữ liệu của tính năng.
+`featureList.js`: Component chứa danh sách dữ liệu của tính năng.
-`components/`: Thư mục này chứa các components được sử dụng chung trong ứng dụng.
-`styles/`: Thư mục này chứa các tệp tin liên quan đến CSS của ứng dụng, bao gồm cả tệp tin CSS chung cho toàn bộ ứng dụng và các tệp tin CSS riêng cho từng component.
-`components/`: Thư mục này chứa các component của ứng dụng, mỗi component được đặt trong một thư mục riêng. Mỗi thư mục component bao gồm ít nhất hai tệp tin là:
+`index.js`: được sử dụng để export component tương ứng.
+`componentName.js`: chứa mã nguồn của componen