Redux trong ReactJS

1. Redux là gì?

Redux là 1 thư viện giúp chúng ta quản lý các state 1 cách tốt hơn. Thay vì phải truyền state qua từng Component thì Redux sẽ tạo ra 1 store duy nhất dùng để thay đổi dữ liệu.

2. Cấu trúc của Redux

redux-architecture.png

Redux gồm 4 thành phần chính là:

  • Store là nơi lưu trữ toàn bộ trạng thái của ứng dụng. Trạng thái này bao gồm dữ liệu như thông tin người dùng, dữ liệu sản phẩm, trạng thái đăng nhập, v.v.

  • Action là các sự kiện (events) mô tả những điều xảy ra trong ứng dụng. Action thường được biểu diễn bằng các đối tượng JavaScript và chứa hai thuộc tính quan trọng: typepayload. Ví dụ:

    { type: 'ADD_PRODUCT', payload: { name: 'Máy tính xách tay' } }
  • Reducer là các hàm xử lý trạng thái của ứng dụng dựa trên các action. Mỗi reducer đảm nhiệm việc xử lý cho một phần của trạng thái của ứng dụng. Chúng xác định cách trạng thái thay đổi khi một action được gửi đến. Một reducer nhận vào trạng thái hiện tại và action, sau đó trả về một trạng thái mới. Ví dụ:

    function productReducer(state = [], action) { switch (action.type) { case 'ADD_PRODUCT': return [...state, action.payload]; // Xử lý các action khác default: return state; } }
  • View: Hiển thị dữ liệu được cung cấp bởi Store.

3. Nguyên lý hoạt động

redux-workflows.gif

Luồng xử lý của Redux như sau:

  • Trường hợp không sử dụng Middleware: State được khởi tạo bên trong Store ->  State được đưa vào Reducer -> Reducer khởi tạo giá trị state ban đầu (initialState)  -> Thực hiện action ở component (dispatch event) -> Thay đổi giá trị của state bên trong Reducer thành state mới -> Đẩy state mới ra ngoài View (component)
  • Trường hợp sử dụng Middleware: State được khởi tạo bên trong Store -> State được đưa vào Reducer -> Reducer khởi tạo giá trị state ban đầu (initialState) -> Thực hiện action ở component (dispatch event) -> Gọi API ở Middleware -> Đưa dữ liệu vừa gọi vào Reducer -> Thay đổi giá trị của state bên trong Reducer thành state mới -> Đẩy state mới ra ngoài View (component).

4. Sử dụng Redux trong ReactJS

Bây giờ chúng ta sẽ tìm hiểu cách tích hợp Redux vào một ứng dụng ReactJS. Hãy đi qua các bước cụ thể:

  • Bước 1: Cài đặt Redux Trước tiên, bạn cần cài đặt Redux và React-Redux vào dự án của mình:

    npm install redux react-redux
  • Bước 2: Định nghĩa Store Tạo một file store.js để định nghĩa store của ứng dụng. Store sẽ chứa toàn bộ trạng thái của ứng dụng và được tạo ra bằng cách sử dụng hàm createStore từ Redux:

    import { createStore } from 'redux'; import rootReducer from './reducers'; // Import reducers const store = createStore(rootReducer); export default store;
  • Bước 3: Định nghĩa Reducers Reducers là nơi xử lý các action và cập nhật trạng thái của ứng dụng. Để quản lý nhiều phần trạng thái khác nhau, chúng ta sử dụng combineReducers để kết hợp các reducers lại với nhau:

    // reducers.js import { combineReducers } from 'redux'; // Reducer cho trạng thái sản phẩm function productReducer(state = [], action) { switch (action.type) { case 'ADD_PRODUCT': return [...state, action.payload]; // Xử lý các action khác default: return state; } } // Reducer cho trạng thái người dùng function userReducer(state = null, action) { switch (action.type) { case 'LOGIN': return action.payload; case 'LOGOUT': return null; // Xử lý các action khác default: return state; } } const rootReducer = combineReducers({ products: productReducer, user: userReducer, }); export default rootReducer;
  • Bước 4: Kết nối React với Redux Để truy cập trạng thái của Redux trong các thành phần React, chúng ta sử dụng connect từ thư viện react-redux. Dưới đây là cách kết nối một thành phần React với Redux và truy cập vào trạng thái:

    //ProductList.js import React from 'react'; import { connect } from 'react-redux'; function ProductList({ products }) { return ( <div> {products.map((product, index) => ( <div key={index}>{product.name}</div> ))} </div> ); } const mapStateToProps = (state) => { return { products: state.products, }; }; export default connect(mapStateToProps)(ProductList);
  • Bước 5: Gửi Action Cuối cùng, để thay đổi trạng thái của ứng dụng, bạn có thể gửi action từ các thành phần của bạn. Dưới đây là một ví dụ về cách gửi một action để thêm một sản phẩm mới:

    //AddProduct.js import React from 'react'; import { connect } from 'react-redux'; function AddProduct({ dispatch }) { const handleAddProduct = () => { const newProduct = { name: 'New Product' }; dispatch({ type: 'ADD_PRODUCT', payload: newProduct }); }; return ( <button onClick={handleAddProduct}>Thêm sản phẩm</button> ); } export default connect()(AddProduct);

Sử dụng useSelectoruseDispatch để tương tác với Redux:

Sử dụng useSelectoruseDispatch là cách hiện đại và đơn giản hơn để tương tác với Redux trong function components. Dưới đây là ví dụ sử dụng chúng:

// ProductList.js import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; function ProductList() { // Sử dụng useSelector để truy xuất trạng thái từ Redux store const products = useSelector((state) => state.products); return ( <div> {products.map((product, index) => ( <div key={index}>{product.name}</div> ))} </div> ); } export default ProductList;
// AddProduct.js import React from 'react'; import { useDispatch } from 'react-redux'; function AddProduct() { const dispatch = useDispatch(); const handleAddProduct = () => { const newProduct = { name: 'New Product' }; // Sử dụng useDispatch để gửi action và thay đổi trạng thái Redux dispatch({ type: 'ADD_PRODUCT', payload: newProduct }); }; return ( <button onClick={handleAddProduct}>Thêm sản phẩm</button> ); } export default AddProduct;

5. Một vài khái niệm khác liên quan đến Redux

5.1 Redux Thunk

Redux Thunk là một phần mềm trung gian (middleware) cho phép trả về các function thay vì chỉ là action trong Redux. Một trong những ứng dụng chính của Redux Thunk là dùng để xử lý action không đồng bộ, chẳng hạn như sử dụng axios để gửi một GET request.

Dưới đây là một ví dụ cơ bản về việc sử dụng Redux với React-Redux và Redux-Thunk trong ứng dụng React.

  • Bước 1: Cài đặt các thư viện cần thiết:

    npm install react react-dom react-redux redux redux-thunk axios
  • Bước 2: Tạo một action và reducer để quản lý dữ liệu sau khi nhận được yêu cầu GET

    // actions.js export const FETCH_DATA_REQUEST = "FETCH_DATA_REQUEST"; export const FETCH_DATA_SUCCESS = "FETCH_DATA_SUCCESS"; export const FETCH_DATA_FAILURE = "FETCH_DATA_FAILURE"; export const fetchDataRequest = () => ({ type: FETCH_DATA_REQUEST, }); export const fetchDataSuccess = (data) => ({ type: FETCH_DATA_SUCCESS, payload: data, }); export const fetchDataFailure = (error) => ({ type: FETCH_DATA_FAILURE, payload: error, });
    // reducer.js import { FETCH_DATA_REQUEST, FETCH_DATA_SUCCESS, FETCH_DATA_FAILURE, } from "./actions"; const initialState = { data: [], loading: false, error: null, }; const dataReducer = (state = initialState, action) => { switch (action.type) { case FETCH_DATA_REQUEST: return { ...state, loading: true, }; case FETCH_DATA_SUCCESS: return { ...state, data: action.payload, loading: false, }; case FETCH_DATA_FAILURE: return { ...state, error: action.payload, loading: false, }; default: return state; } }; export default dataReducer;
  • Bước 3: Tạo Redux Store và sử dụng Redux-Thunk trong store.js

    // store.js import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import dataReducer from "./reducer"; const store = createStore(dataReducer, applyMiddleware(thunk)); export default store;
  • Bước 4: Tạo một thành phần React để hiển thị dữ liệu từ yêu cầu GET

    // DataDisplay.js import React, { useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { fetchDataRequest, fetchDataSuccess, fetchDataFailure } from "./actions"; import axios from "axios"; const DataDisplay = () => { const dispatch = useDispatch(); const { data, loading, error } = useSelector((state) => state); useEffect(() => { dispatch(fetchDataRequest()); axios .get("https://api.example.com/data") .then((response) => { dispatch(fetchDataSuccess(response.data)); }) .catch((error) => { dispatch(fetchDataFailure(error.message)); }); }, [dispatch]); if (loading) { return <p>Loading...</p>; } if (error) { return <p>Error: {error}</p>; } return ( <div> <h2>Data Display</h2> <ul> {data.map((item, index) => ( <li key={index}>{item.name}</li> ))} </ul> </div> ); }; export default DataDisplay;
  • Bước 5: Tạo thành phần chính và kết nối nó với Redux Store

    // App.js import React from "react"; import { Provider } from "react-redux"; import store from "./store"; import DataDisplay from "./DataDisplay"; function App() { return ( <Provider store={store}> <div className="App"> <DataDisplay /> </div> </Provider> ); } export default App;

5.2 Redux Persist

Redux Persist là một thư viện cho phép lưu Redux store trong bộ nhớ cục bộ của ứng dụng. Với các thư viện quản lý trạng thái như Redux, người dùng có thể quản lý trạng thái của ứng dụng chỉ từ một nơi duy nhất. Khi ứng dụng phát triển dần về mặt tính năng, ta có thể cần duy trì một số thông tin cục bộ cho từng người dùng.

Lấy ví dụ, giả sử rằng ta cần xây dựng một ứng dụng giỏ hàng cho nền tảng mua sắm, và ứng dụng này yêu cầu việc duy trì dữ liệu liên quan đến sản phẩm mà người dùng đang thêm vào giỏ trước khi thực hiện đặt hàng. Nếu người dùng đóng ứng dụng trước khi đặt hàng, thì ta nên thiết kế ứng dụng sao cho các sản phẩm trước đó vẫn còn tồn tại trong giỏ hàng người dùng để đem lại trải nghiệm tốt nhất. Trong trường hợp này, ta có thể lưu các sản phẩm này trong bộ nhớ cục bộ của người dùng. Đây chính là ứng dụng phổ biến nhất của Redux Persist.

Bên cạnh đó, việc sử dụng Redux Persist cho phép thực hiện công việc hoàn toàn tự động chỉ với một số lượng bản ghi nhỏ cho quá trình khởi tạo. Từ đó quy trình quản lý ứng dụng với Redux sẽ còn trở nên đơn giản, hiệu quả hơn nữa.


Tài liệu tham khảo: