Tất tần tật về Redux trong React: Định nghĩa, Nguyên lý và Ví dụ chi tiết

VnnTools

Bạn là một lập trình viên React? Bạn đã từng nghe đến Redux và cảm thấy hoang mang? Hay bạn đang xây dựng một ứng dụng React và bắt đầu "phát điên" vì phải truyền props qua hàng chục component khác nhau?

Nếu câu trả lời là "Có", thì bạn đã đến đúng nơi.

State trong ReactJS: Khái niệm, cách sử dụng và ví dụ chi tiết

Bài viết này sẽ giúp bạn giải mã mọi thứ về Redux và sự kết hợp của nó với ReactJS. Chúng ta sẽ cùng nhau đi từ những khái niệm cơ bản nhất, khám phá lý do tại sao Redux ra đời, cách nó hoạt động, và quan trọng nhất, khi nào bạn thực sự cần đến nó.

Hãy bắt đầu hành trình này!

1. Cơn ác mộng mang tên "Prop Drilling" và sự ra đời của Redux

Hãy tưởng tượng bạn đang xây dựng một ứng dụng lớn, ví dụ như một trang thương mại điện tử. Bạn có thông tin người dùng (họ tên, ảnh đại diện, giỏ hàng) ở component gốc App. Bây giờ, bạn muốn hiển thị tên người dùng ở Header và số lượng sản phẩm trong giỏ hàng ở một component MiniCart nằm sâu bên trong Header.

Theo cách thông thường của React, bạn sẽ phải làm gì?

  1. Truyền userInfo từ App xuống HomePage.
  2. Truyền userInfo từ HomePage xuống Header.
  3. Header sử dụng userInfo.name và lại tiếp tục truyền userInfo.cart xuống component MiniCart.

Cái chuỗi truyền dữ liệu dài dằng dặc qua nhiều tầng component trung gian không hề cần đến nó được gọi là "Prop Drilling".

Đây chính là vấn đề lớn mà Redux được sinh ra để giải quyết.

Redux là gì? Một cách định nghĩa dễ hiểu nhất

Redux là một thư viện quản lý trạng thái (state management) có thể dự đoán được cho các ứng dụng JavaScript. Nó không phải là một phần của React, mà là một thư viện độc lập có thể dùng với React, Angular, Vue hay thậm chí là Vanilla JS.

Hãy hình dung Redux như một "kho chứa đồ chung" cho toàn bộ ứng dụng của bạn. Thay vì mỗi component tự giữ trạng thái riêng và truyền cho nhau một cách lộn xộn, tất cả trạng thái quan trọng (như thông tin người dùng, cài đặt theme, dữ liệu sản phẩm...) sẽ được lưu trữ tại một nơi duy nhất gọi là Store.

Bất kỳ component nào, dù ở sâu đến đâu trong cây component, đều có thể "kết nối" trực tiếp với "kho" này để lấy dữ liệu nó cần hoặc yêu cầu thay đổi dữ liệu mà không cần phải đi qua các component trung gian.

Phần 2: Ba khái niệm "core" của Redux

Để hiểu Redux, bạn cần nắm vững 3 khái niệm cốt lõi. Chúng tạo thành một luồng dữ liệu một chiều (unidirectional data flow) rất rõ ràng và dễ gỡ lỗi.

1. Store

Đây là trái tim của Redux. Store là một đối tượng JavaScript khổng lồ chứa toàn bộ trạng thái (state) của ứng dụng.

  • Duy nhất: Toàn bộ ứng dụng của bạn chỉ có duy nhất MỘT Store. Đây được gọi là "Single Source of Truth".
  • Chỉ đọc (Read-only): Bạn không bao giờ được phép thay đổi trực tiếp state trong Store. Ví dụ: store.state.user = 'new user'CẤM.
  • Cách thay đổi: Cách duy nhất để thay đổi state là gửi đi một "Action".

2. Actions

Nếu bạn muốn thay đổi trạng thái trong Store, bạn phải "gửi yêu cầu". Action chính là bản yêu cầu đó.

  • Là một object JavaScript đơn thuần: Một Action phải có thuộc tính type để mô tả hành động, ví dụ: 'auth/loginSuccess', 'cart/addProduct'.
  • Có thể chứa dữ liệu kèm theo (payload): Ngoài type, Action có thể mang theo dữ liệu cần thiết cho việc thay đổi state. Dữ liệu này thường được đặt trong thuộc tính payload.

Ví dụ một Action:

// Action để thêm một sản phẩm vào giỏ hàng
{
  type: 'cart/addProduct',
  payload: {
    productId: 'prod_123',
    name: 'Laptop ABC',
    price: 20000000
  }
}

3. Reducers

Khi một Action được gửi đi (dispatch), Redux sẽ chuyển nó đến cho Reducer.

  • Là một hàm thuần túy (Pure Function): Reducer nhận vào 2 tham số: (currentState, action) và trả về một newState.
  • Không bao giờ thay đổi state cũ: Thay vì sửa state hiện tại, Reducer phải tạo ra một bản sao của state, thay đổi trên bản sao đó và trả về bản sao mới. Đây là nguyên tắc bất biến (immutability).
  • Xử lý dựa trên action.type: Bên trong Reducer, bạn sẽ dùng switch...case hoặc if...else để kiểm tra action.type và quyết định cách cập nhật state.

Ví dụ một Reducer đơn giản:

const initialState = {
  items: [],
  totalAmount: 0,
}

function cartReducer(state = initialState, action) {
  switch (action.type) {
    case 'cart/addProduct':
      // Tạo một bản sao mới của state
      const newState = {
        ...state,
        // Thêm sản phẩm mới vào mảng items
        items: [...state.items, action.payload],
        // Cập nhật tổng tiền
        totalAmount: state.totalAmount + action.payload.price,
      }
      // Trả về state mới
      return newState
    default:
      // Nếu action không khớp, trả về state hiện tại
      return state
  }
}

Luồng hoạt động đầy đủ:

  1. UI Event: Người dùng click vào nút "Thêm vào giỏ hàng".
  2. Dispatch Action: Component gọi hàm dispatch() để gửi đi một Action { type: 'cart/addProduct', payload: {...} }.
  3. Reducer xử lý: Store nhận Action và chuyển cho cartReducer. Reducer này xử lý và trả về một state mới của giỏ hàng.
  4. Store cập nhật: Store thay thế state cũ bằng state mới mà Reducer vừa trả về.
  5. UI cập nhật: Các component React nào đang "lắng nghe" sự thay đổi của giỏ hàng sẽ tự động được render lại với dữ liệu mới.

Phần 3: React Redux - Cầu nối hoàn hảo

Như đã nói, Redux là một thư viện độc lập. Để kết nối nó với React một cách hiệu quả, chúng ta sử dụng thư viện react-redux. Thư viện này cung cấp các hook và component giúp việc tương tác trở nên dễ dàng.

  • <Provider store={store}>: Một component dùng để bọc toàn bộ ứng dụng của bạn (<App />). Nó giúp mọi component con có thể truy cập vào Redux store.
  • useSelector(): Một hook cho phép component "đọc" hoặc "lấy" dữ liệu từ store. Nó nhận vào một hàm để chọn ra mảnh state mà component cần.
  • useDispatch(): Một hook trả về hàm dispatch của store. Bạn dùng hàm này để gửi đi các Actions.

Phần 4: Redux Toolkit (RTK) - Phiên bản nâng cấp

Nhiều lập trình viên cho rằng Redux "cổ điển" khá dài dòng và phải viết nhiều mã lặp đi lặp lại (boilerplate). Nhận biết điều này, đội ngũ Redux đã tạo ra Redux Toolkit (RTK).

Redux Toolkit là bộ công cụ chính thức, được đề xuất để phát triển ứng dụng Redux. Nó giúp đơn giản hóa và tăng tốc quá trình code Redux.

RTK giải quyết các vấn đề của Redux cũ bằng cách:

  • Giảm code boilerplate: Với hàm createSlice, bạn có thể định nghĩa Reducer và Actions trong cùng một file một cách ngắn gọn.
  • Cấu hình Store đơn giản: configureStore tự động thiết lập những thứ cần thiết, bao gồm cả Redux DevTools.
  • Tích hợp sẵn ImmerJS: Giúp bạn viết code thay đổi state trông như thể bạn đang sửa trực tiếp (mutable), nhưng thực chất nó vẫn đảm bảo tính bất biến (immutable) ở phía sau.

Nếu bạn bắt đầu với Redux hôm nay, hãy sử dụng Redux Toolkit.

// Tạo Slice với createSlice
import { createSlice } from '@reduxjs/toolkit'

const cartSlice = createSlice({
  name: 'cart',
  initialState: {
    items: [],
    totalAmount: 0,
    isLoading: false,
  },
  reducers: {
    addProduct: (state, action) => {
      // Với Immer, bạn có thể "sửa" state trực tiếp
      state.items.push(action.payload)
      state.totalAmount += action.payload.price
    },
    removeProduct: (state, action) => {
      const index = state.items.findIndex(
        (item) => item.id === action.payload.id,
      )
      if (index !== -1) {
        const removedItem = state.items[index]
        state.items.splice(index, 1)
        state.totalAmount -= removedItem.price
      }
    },
    clearCart: (state) => {
      state.items = []
      state.totalAmount = 0
    },
    setLoading: (state, action) => {
      state.isLoading = action.payload
    },
  },
})

// Tự động tạo ra actions
export const { addProduct, removeProduct, clearCart, setLoading } =
  cartSlice.actions

// Tự động tạo ra reducer
export default cartSlice.reducer

Phần 5: Ưu, nhược điểm và khi nào nên dùng Redux?

Redux rất mạnh mẽ, nhưng không phải là "viên đạn bạc" cho mọi dự án.

Ưu điểm:

  • Dự đoán được (Predictable): Luồng dữ liệu một chiều giúp bạn dễ dàng biết được state thay đổi ở đâu, khi nào và tại sao.
  • Quản lý tập trung: Mọi thứ ở một nơi, dễ dàng quản lý và mở rộng.
  • Gỡ lỗi (Debugging) tuyệt vời: Với Redux DevTools, bạn có thể "time travel", xem lại từng action và sự thay đổi của state.
  • Hệ sinh thái lớn: Rất nhiều thư viện hỗ trợ (redux-persist, redux-saga, redux-thunk...).

Nhược điểm:

  • Đường cong học tập (Learning Curve): Cần thời gian để hiểu rõ các khái niệm.
  • Dài dòng (Verbosity): Ngay cả với RTK, việc thiết lập cho các tác vụ đơn giản vẫn có thể tốn nhiều công sức hơn so với state nội bộ của component.
  • Không dành cho tất cả: Dùng Redux cho một ứng dụng nhỏ, đơn giản là "dùng dao mổ trâu để giết gà".

Vậy khi nào nên dùng Redux?

  • Ứng dụng lớn và phức tạp: Khi state được chia sẻ bởi nhiều component không liên quan trực tiếp.
  • Trạng thái ứng dụng thay đổi thường xuyên: Cần một cơ chế quản lý chặt chẽ.
  • Cần các tính năng gỡ lỗi nâng cao: Như time-travel debugging.
  • Làm việc trong đội nhóm lớn: Cấu trúc rõ ràng của Redux giúp các thành viên dễ dàng phối hợp.

Các giải pháp thay thế Redux

  • React Context API: Tích hợp sẵn trong React, tốt cho việc truyền dữ liệu đơn giản, tránh prop drilling ở quy mô nhỏ.
  • Zustand, Jotai: Các thư viện quản lý state hiện đại, tối giản và ít boilerplate hơn Redux.
  • MobX: Một giải pháp mạnh mẽ khác dựa trên lập trình phản ứng (Reactive Programming).

Redux không phải là một khái niệm đáng sợ. Nó là một công cụ mạnh mẽ, một giải pháp kiến trúc tuyệt vời cho bài toán quản lý trạng thái trong các ứng dụng JavaScript quy mô lớn. Bằng cách tập trung toàn bộ state vào một "kho" duy nhất và áp đặt một luồng dữ liệu một chiều, Redux mang lại sự rõ ràng, dễ dự đoán và khả năng gỡ lỗi phi thường.

Với sự ra đời của Redux Toolkit, việc sử dụng Redux đã trở nên đơn giản và hiệu quả hơn bao giờ hết.

Hy vọng rằng qua bài viết này, bạn không chỉ hiểu "Redux trong React là gì" mà còn có thể tự tin quyết định xem nó có phù hợp với dự án tiếp theo của mình hay không.

Chúc bạn thành công trên con đường chinh phục React và Redux!

Bài viết liên quan

State trong ReactJS: Khái niệm, cách sử dụng và ví dụ chi tiết

Tìm hiểu khái niệm, cách khai báo và sử dụng State để quản lý dữ liệu động trong ứng dụng React của bạn. Xem ngay hướng dẫn chi tiết kèm ví dụ.

Two-way Binding trong React: Cách hoạt động & Ví dụ minh họa dễ hiểu

Hướng dẫn chi tiết về two-way binding trong React. Bài viết phân tích cách nó hoạt động, giải thích sự khác biệt so với các framework khác và cung cấp ví dụ thực tế.

JSX trong React là gì? Hướng dẫn chi tiết cho người mới bắt đầu

JSX là cú pháp mở rộng của JavaScript, cho phép viết code giống HTML trong React. Bài viết này sẽ giúp bạn hiểu rõ JSX là gì, tại sao cần dùng JSX và cách sử dụng hiệu quả.

React Hook là gì? Hướng dẫn chi tiết cho người mới bắt đầu

Bạn đang tìm hiểu về React Hook? Bài viết này sẽ giải thích React Hook là gì, các loại Hook phổ biến và hướng dẫn từng bước cách áp dụng chúng vào dự án thực tế.