Skip to main content

Khi State Management không còn là cơn ác mộng

Posted on:  at 
Tutorials
Picture

Chào các bạn developer! Hôm nay chúng ta sẽ nói về Redux - một thứ mà nghe tên thôi đã thấy "cao siêu" rồi. Nhưng đừng lo, Redux không phải là một con quái vật khổng lồ như các bạn tưởng đâu. Nó chỉ là một anh chàng hơi... cầu kỳ một chút thôi!

Bạn có bao giờ cảm thấy như đang chơi "đuổi bắt" với state trong React không? Hôm nay component A cần data, mai component B lại muốn update cái data đó, mà lại còn phải truyền props qua 5-6 cấp component như chơi "cầu chuyền"? Nếu có, thì Redux chính là "siêu anh hùng" mà bạn cần!

Redux Là Gì Vậy?

Redux là một thư viện quản lý state (trạng thái) cho các ứng dụng JavaScript. Nó hoạt động theo nguyên tắc "tập trung hóa" - tức là tất cả state của app sẽ được lưu trữ ở một nơi duy nhất gọi là Store.

Hình dung Redux như một cái kho khổng lồ, và tất cả các component trong app của bạn đều có thể:

  • Đọc data từ kho này
  • Gửi yêu cầu để thay đổi data trong kho
  • Được thông báo khi data trong kho thay đổi

Tại Sao Lại Cần Redux?

Vấn đề với State Management truyền thống

Khi app React của bạn nhỏ xinh, việc quản lý state khá đơn giản. Nhưng khi app lớn lên, bạn sẽ gặp những vấn đề như:

  1. Props Drilling: Truyền props qua nhiều cấp component, ngay cả khi component ở giữa không cần dùng đến
  2. State rải rác: State bị phân tán ở nhiều nơi, khó theo dõi và debug
  3. Chia sẻ state khó khăn: Hai component anh em muốn chia sẻ state thì phải "leo" lên component cha chung

Redux giải quyết như thế nào?

Redux đưa ra một kiến trúc rõ ràng với 3 nguyên tắc vàng:

  1. Single Source of Truth: Toàn bộ state được lưu trong một Store duy nhất
  2. State is Read-Only: Muốn thay đổi state thì phải dispatch (gửi) một Action
  3. Changes are Made with Pure Functions: Sử dụng Reducer để mô tả cách state thay đổi

Các Khái Niệm Cốt Lõi

1. Store - Kho Chứa Tất Cả

Store là trung tâm của Redux, nơi lưu trữ toàn bộ state tree của ứng dụng.

import { createStore } from 'redux';

const store = createStore(reducer);

2. Action - Thông Điệp Hành Động

Action là một plain object mô tả "cái gì đã xảy ra" trong app.

// Action creator
const addTodo = (text) => ({
  type: 'ADD_TODO',
  payload: {
    id: Date.now(),
    text: text,
    completed: false
  }
});

3. Reducer - Bộ Xử Lý Thay Đổi

Reducer là pure function nhận vào state hiện tại và action, trả về state mới.

const todosReducer = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, action.payload];
    case 'TOGGLE_TODO':
      return state.map(todo =>
        todo.id === action.payload.id
          ? { ...todo, completed: !todo.completed }
          : todo
      );
    default:
      return state;
  }
};

4. Dispatch - Gửi Hành Động

Dispatch là cách duy nhất để trigger một state change.

store.dispatch(addTodo('Học Redux cho vui'));

Redux Flow - Luồng Hoạt Động

Luồng hoạt động của Redux rất rõ ràng và có thể dự đoán được:

  1. UI dispatch một Action
  2. Store gọi Reducer với state hiện tại và action
  3. Reducer tính toán và trả về state mới
  4. Store lưu state mới và thông báo cho tất cả subscribers
  5. UI re-render với state mới
UI → Action → Dispatch → Reducer → New State → UI Update

Ví Dụ Thực Tế: Todo App

Hãy xây dựng một Todo App đơn giản để hiểu rõ hơn về Redux:

Bước 1: Định nghĩa Actions

// Action types
const ADD_TODO = 'ADD_TODO';
const TOGGLE_TODO = 'TOGGLE_TODO';
const DELETE_TODO = 'DELETE_TODO';

// Action creators
export const addTodo = (text) => ({
  type: ADD_TODO,
  payload: { id: Date.now(), text, completed: false }
});

export const toggleTodo = (id) => ({
  type: TOGGLE_TODO,
  payload: { id }
});

export const deleteTodo = (id) => ({
  type: DELETE_TODO,
  payload: { id }
});

Bước 2: Tạo Reducer

const initialState = {
  todos: [],
  filter: 'ALL' // ALL, ACTIVE, COMPLETED
};

const todoReducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_TODO:
      return {
        ...state,
        todos: [...state.todos, action.payload]
      };
    
    case TOGGLE_TODO:
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload.id
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      };
    
    case DELETE_TODO:
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.payload.id)
      };
    
    default:
      return state;
  }
};

Bước 3: Tạo Store

import { createStore } from 'redux';

const store = createStore(todoReducer);

Redux với React

Để sử dụng Redux với React, bạn cần thêm react-redux:

npm install react-redux

Provider Component

Wrap app của bạn với Provider để cung cấp store cho tất cả components:

import { Provider } from 'react-redux';
import store from './store';

function App() {
  return (
    <Provider store={store}>
      <TodoApp />
    </Provider>
  );
}

useSelector và useDispatch Hooks

import { useSelector, useDispatch } from 'react-redux';
import { addTodo, toggleTodo } from './actions';

function TodoList() {
  const todos = useSelector(state => state.todos);
  const dispatch = useDispatch();

  const handleAddTodo = (text) => {
    dispatch(addTodo(text));
  };

  const handleToggleTodo = (id) => {
    dispatch(toggleTodo(id));
  };

  return (
    <div>
      {todos.map(todo => (
        <div key={todo.id} onClick={() => handleToggleTodo(todo.id)}>
          {todo.text} - {todo.completed ? '✅' : '⏳'}
        </div>
      ))}
    </div>
  );
}

Redux Toolkit - Cách Hiện Đại

Redux Toolkit (RTK) là cách được khuyến nghị để viết Redux logic hiện nay. Nó giúp code ngắn gọn và ít lỗi hơn:

npm install @reduxjs/toolkit
import { createSlice, configureStore } from '@reduxjs/toolkit';

// Tạo slice
const todoSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    addTodo: (state, action) => {
      state.push({
        id: Date.now(),
        text: action.payload,
        completed: false
      });
    },
    toggleTodo: (state, action) => {
      const todo = state.find(todo => todo.id === action.payload);
      if (todo) {
        todo.completed = !todo.completed;
      }
    }
  }
});

// Export actions và reducer
export const { addTodo, toggleTodo } = todoSlice.actions;
export default todoSlice.reducer;

// Tạo store
const store = configureStore({
  reducer: {
    todos: todoSlice.reducer
  }
});

Ưu Điểm và Nhược Điểm

✅ Ưu điểm:

  • Predictable: State changes có thể dự đoán được
  • Debuggable: Dễ debug với Redux DevTools
  • Testable: Pure functions dễ test
  • Time Travel: Có thể "du hành thời gian" với state
  • Centralized: Tất cả state ở một chỗ

❌ Nhược điểm:

  • Boilerplate: Khá nhiều code setup ban đầu
  • Learning Curve: Khái niệm mới, cần thời gian học
  • Overkill: Có thể thừa thãi cho app nhỏ
  • Performance: Có thể gây re-render không cần thiết nếu không tối ưu

Khi Nào Nên Dùng Redux?

✅ Nên dùng khi:

  • App có nhiều state cần chia sẻ giữa nhiều components
  • State thay đổi thường xuyên theo thời gian
  • Logic update state phức tạp
  • App có kích thước trung bình đến lớn
  • Cần debug state changes
  • Làm việc trong team lớn

❌ Không nên dùng khi:

  • App đơn giản với ít state
  • State chỉ cần trong vài components
  • Bạn mới học React (nên thành thạo React trước)
  • Deadline gấp và team chưa familiar với Redux

Kết Luận

Redux là một công cụ mạnh mẽ cho state management, nhưng không phải lúc nào cũng cần thiết. Hãy cân nhắc kỹ trước khi quyết định sử dụng.

Nhớ rằng: "With great power comes great responsibility" - Redux mang lại sức mạnh lớn, nhưng cũng đòi hỏi bạn phải hiểu rõ và sử dụng đúng cách.

Chúc các bạn code vui vẻ với Redux! Và nhớ rằng, mọi thứ đều cần thời gian để thành thạo. Đừng nản lòng nếu ban đầu thấy khó nhé! 🚀


P/S: Nếu bạn thấy Redux quá phức tạp, hãy thử Zustand hoặc Context API trước. Không có gì xấu hổ cả, mỗi tool đều có lúc phù hợp! 😊