Command Palette

Search for a command to run...

Quản lý Global State trong React Native: Redux Toolkit hay Zustand?

Quản lý Global State trong React Native

Ở bài trước, chúng ta đã học cách ghi nhớ dữ liệu vào ổ cứng điện thoại bằng AsyncStorage. Nhưng đó là dữ liệu lưu trữ lâu dài. Vậy còn những dữ liệu cần phản hồi ngay lập tức trên màn hình trong lúc người dùng đang thao tác thì sao?

Hãy tưởng tượng một kịch bản vô cùng quen thuộc: Bạn đang làm một ứng dụng mua sắm. Ở trang Chi tiết sản phẩm, người dùng bấm nút "Thêm vào giỏ hàng". Ngay lập tức, biểu tượng Giỏ hàng nằm ở tít trên góc phải của thanh Header (thuộc một màn hình hoàn toàn khác) phải hiển thị số 1.

Làm thế nào để hai màn hình không liên quan gì đến nhau lại có thể "nháy mắt" và truyền dữ liệu cho nhau trong chớp nhoáng? Chào mừng bạn đến với thế giới của Global State (Quản lý trạng thái toàn cục)!

1. Nỗi đau mang tên "Prop Drilling"

Nếu chỉ dùng useState như ở những bài học đầu tiên, dữ liệu của bạn chỉ sống bên trong một Component duy nhất. Để chia sẻ dữ liệu đó cho một Component khác, bạn phải truyền nó qua các thuộc tính (Props) từ cha xuống con.

"Prop Drilling" trong React Native

Khi ứng dụng lớn lên, cây thư mục của bạn có thể sâu đến 5-6 tầng. Việc bạn phải truyền một biến cartCount từ App ➔ Home ➔ List ➔ Item ➔ Button chỉ để thằng cháu chắt ở dưới cùng sử dụng được gọi là Prop Drilling (Khoan giếng truyền prop).

Điều này biến code của bạn thành một mớ bòng bong cực kỳ khó bảo trì. Cứ mỗi lần sửa tên biến, bạn phải đi sửa lại ở 6 file khác nhau!

2. Giải pháp: "Đám mây" Global State

Để chấm dứt nỗi đau này, các kỹ sư phần mềm đã tạo ra khái niệm Global State (hay còn gọi là Store).

Hãy tưởng tượng Store giống như một trạm phát sóng vô tuyến đặt trên mây, nằm độc lập và cao hơn tất cả các màn hình của bạn.

  1. Khi nút "Thêm vào giỏ" được bấm, nó bắn một tín hiệu lên trạm phát sóng: "Ê, cộng 1 vào giỏ hàng nhé!"
  2. Trạm phát sóng cập nhật con số.
  3. Biểu tượng Giỏ hàng trên Header (đang dò sóng của trạm) ngay lập tức nhận được tín hiệu và tự động cập nhật số 1 lên màn hình, bất kể nó nằm ở đâu trong ứng dụng.

Global State trong React Native

Hiện tại, có hai "trạm phát sóng" quyền lực nhất trong giới React Native: Redux ToolkitZustand.

3. Redux Toolkit (RTK): Lựa chọn của các dự án lớn

Nhắc đến React, không ai không biết đến Redux. Redux từng bị ghét vì phải viết code thiết lập (boilerplate) quá dài dòng. Nhưng sự ra đời của Redux Toolkit (RTK) đã thay đổi tất cả. RTK là tiêu chuẩn vàng hiện tại, giúp viết Redux nhanh và nhàn hơn gấp 10 lần.

Redux Toolkit

Kiến trúc cốt lõi của Redux

Để hiểu Redux, hãy nghĩ đến cách bạn gửi tiền ở ngân hàng:

  • Store (Két sắt): Nơi chứa toàn bộ dữ liệu. Bạn không được tự thò tay vào lấy hay cất tiền.
  • Action (Phiếu yêu cầu): Tờ giấy bạn ghi "Tôi muốn nộp 50k".
  • Dispatch (Nộp phiếu): Hành động bạn đưa tờ phiếu cho nhân viên ngân hàng.
  • Reducer (Nhân viên ngân hàng): Người duy nhất được phép mở két sắt (Store), đọc phiếu (Action) và tính toán lại số tiền của bạn.

Cách cài đặt và sử dụng

Bước 1: Cài đặt

npm install @reduxjs/toolkit react-redux

Bước 2: Tạo một Slice (Một ngăn của Két sắt) Slice là cách RTK gom Action và Reducer lại làm một chỗ cho gọn gàng.

// cartSlice.js
import { createSlice } from '@reduxjs/toolkit'

export const cartSlice = createSlice({
  name: 'cart', // Tên của ngăn két
  initialState: { count: 0 }, // Dữ liệu ban đầu
  reducers: {
    // Các nhân viên ngân hàng (hàm xử lý)
    addToCart: (state) => {
      state.count += 1 // Thêm 1 sản phẩm
    },
    clearCart: (state) => {
      state.count = 0 // Xóa sạch giỏ
    },
  },
})

export const { addToCart, clearCart } = cartSlice.actions
export default cartSlice.reducer

Bước 3: Sử dụng trong Component Bất kỳ màn hình nào cũng có thể gọi dữ liệu hoặc thay đổi dữ liệu cực kỳ dễ dàng bằng 2 Hooks: useSelector (để đọc) và useDispatch (để ra lệnh).

import { Text, TouchableOpacity } from 'react-native'
import { useSelector, useDispatch } from 'react-redux'
import { addToCart } from './cartSlice'

export default function ProductButton() {
  const dispatch = useDispatch() // Gọi anh nhân viên ngân hàng

  return (
    <TouchableOpacity onPress={() => dispatch(addToCart())}>
      <Text>Thêm vào giỏ</Text>
    </TouchableOpacity>
  )
}

4. Zustand: Ngôi sao mới nổi (nhanh, gọn, nhẹ)

Dù Redux Toolkit đã rất tốt, nhiều lập trình viên vẫn cảm thấy nó hơi "cồng kềnh" nếu ứng dụng không quá phức tạp. Đó là lúc Zustand (tiếng Đức nghĩa là "Trạng thái") bước lên sàn diễn và lập tức trở thành hiện tượng.

Zustand

Zustand cắt bỏ hoàn toàn khái niệm Action, Reducer, Provider rườm rà. Nó cho phép bạn tạo một trạm phát sóng chỉ bằng vài dòng code cực thuần React Hooks!

Bước 1: Cài đặt

npm install zustand

Bước 2: Tạo Store trong 1 nốt nhạc

// store.js
import { create } from 'zustand'

export const useCartStore = create((set) => ({
  count: 0, // Dữ liệu
  addToCart: () => set((state) => ({ count: state.count + 1 })), // Hàm xử lý
  clearCart: () => set({ count: 0 }),
}))

Bước 3: Dùng thẳng ở mọi nơi

import { Text, TouchableOpacity } from 'react-native'
import { useCartStore } from './store'

export default function Header() {
  // Lấy dữ liệu đếm
  const count = useCartStore((state) => state.count)
  return <Text>Giỏ hàng: {count}</Text>
}

export function ProductButton() {
  // Lấy hàm thêm vào giỏ
  const addToCart = useCartStore((state) => state.addToCart)
  return (
    <TouchableOpacity onPress={addToCart}>
      <Text>Thêm vào giỏ</Text>
    </TouchableOpacity>
  )
}

Tuyệt vời chưa? Không cần bọc ứng dụng trong <Provider>, không cần chia file lằng nhằng. Gọi là chạy!

5. Cuộc chiến lựa chọn: Redux Toolkit hay Zustand?

Bạn đang phân vân không biết nên đưa công cụ nào vào dự án của mình? Đây là lời khuyên thực chiến:

Tiêu chíRedux ToolkitZustand
Độ khó họcKhá khó (Nhiều khái niệm)Cực dễ (Như dùng useState)
Lượng code (Boilerplate)Trung bình - NhiềuRất ít
Công cụ gỡ lỗi (DevTools)Xuất sắc, chi tiết từng bướcTốt, nhưng không mạnh bằng Redux
Cộng đồng & Tài liệuKhổng lồ (Được mọi công ty dùng)Đang tăng trưởng mạnh mẽ
Khi nào nên chọn?App doanh nghiệp lớn, logic cực kỳ phức tạp, team đông người.App vừa và nhỏ, làm dự án cá nhân, cần code nhanh và sạch.

Khuyến nghị cho series học này: Nếu bạn đang học để xin việc tại các công ty lớn, hãy nắm chắc Redux Toolkit vì nó xuất hiện trong 80% JD tuyển dụng. Nếu bạn đang tự làm sản phẩm (Indie hacker) hoặc startup cần ra mắt nhanh, hãy chọn Zustand.

Kết luận: Global State giúp mọi thứ có tổ chức

Global State là chìa khóa để kiến trúc dữ liệu của ứng dụng trở nên gọn gàng, có tổ chức. Nó giải thoát bạn khỏi cơn ác mộng "Prop Drilling", giúp mọi màn hình có thể giao tiếp với nhau theo thời gian thực.

Tuy nhiên, có một bí mật mà ít người mới biết: Cả Redux và Zustand đều RẤT DỞ trong việc quản lý dữ liệu lấy từ API (như xử lý trạng thái Loading, báo lỗi mạng, lưu cache bộ nhớ).

Để giải quyết triệt để vấn đề gọi API, chúng ta sẽ cần đến một "phép màu" mang tên React Query (TanStack Query). Hãy chuẩn bị tinh thần đón xem những bài học thú vị sắp tới nhé!

Bài viết liên quan

Tối ưu việc gọi API trong React Native bằng React Query

Hướng dẫn tối ưu hóa luồng gọi API trong React Native với TanStack Query. Khám phá cách sử dụng useQuery để quản lý Server State, tạo bộ nhớ đệm (cache) tự động siêu mượt.

Cách dùng FlatList trong React Native: Hiển thị danh sách không giật lag

Giải quyết bài toán hiển thị danh sách dữ liệu lớn với FlatList và SectionList. Hướng dẫn tối ưu hóa bộ nhớ và tốc độ cuộn (scroll) mượt mà.

Animation trong React Native: Hướng dẫn toàn tập Reanimated 3

Hướng dẫn cách khắc phục lỗi giật lag UI bằng React Native Reanimated 3. Khám phá bí quyết tạo animation mượt mà 60fps với useSharedValue và withSpring.

Hướng dẫn cài đặt môi trường React Native & Expo trong 15 phút

Bạn sợ cài đặt môi trường lập trình mobile phức tạp? Hướng dẫn chi tiết cách dùng Expo Go, Node.js để chạy app React Native ngay lập tức.