Chào mừng bạn quay trở lại với series “React Roadmap”! Chúng ta đã cùng nhau đi qua rất nhiều kiến thức nền tảng quan trọng của React, từ React là gì, sự khác biệt giữa Class và Functional Components, cách sử dụng JSX, làm quen với Props và State, cho đến các kỹ thuật nâng cao hơn như Conditional Rendering, kết hợp Components, vòng đời Component, làm việc với danh sách và Key, xử lý sự kiện, Refs, các mẫu thiết kế nâng cao.
Một trong những chủ đề trọng tâm mà chúng ta đã và đang khám phá là quản lý trạng thái (state management). Chúng ta đã bắt đầu từ việc quản lý state cục bộ với useState
và useReducer
, sau đó mở rộng phạm vi với useContext
để quản lý global state ở quy mô nhỏ hơn. Đối với các ứng dụng lớn và phức tạp hơn, Redux (đặc biệt là với Redux Toolkit) từ lâu đã là giải pháp “quốc dân”. Tuy nhiên, thế giới frontend không ngừng phát triển, và các thư viện quản lý state mới, hiện đại hơn đã xuất hiện, hứa hẹn giải quyết một số nhược điểm của Redux truyền thống và tận dụng tốt hơn sức mạnh của React hiện đại (đặc biệt là Hooks và các tính năng tiềm năng như Concurrent Mode).
Trong bài viết này, chúng ta sẽ cùng tìm hiểu về hai cái tên nổi bật trong số đó: Recoil và Zustand. Đây là những lựa chọn đang ngày càng phổ biến và đáng để cân nhắc khi bạn xây dựng ứng dụng React của mình. Chúng ta sẽ xem xét cách chúng hoạt động, ưu điểm, nhược điểm, và khi nào nên sử dụng chúng. Đây là bước tiếp theo trong hành trình React Roadmap của bạn.
Mục lục
Tại Sao Cần Các Lựa Chọn Thay Thế Redux?
Redux đã phục vụ cộng đồng React rất tốt trong nhiều năm. Với kiến trúc Flux chặt chẽ (Action -> Dispatcher -> Store -> View), nó mang lại sự dự đoán và quản lý state tập trung hiệu quả cho các ứng dụng quy mô lớn. Redux Toolkit ra đời đã giảm đáng kể lượng boilerplate code và đơn giản hóa việc cấu hình, giúp Redux thân thiện hơn rất nhiều.
Tuy nhiên, Redux truyền thống vẫn còn một số điểm mà các giải pháp mới cố gắng cải thiện:
- Boilerplate và Độ Phức Tạp: Mặc dù Redux Toolkit đã giúp ích, việc thiết lập Redux vẫn đòi hỏi hiểu biết về Actions, Reducers, Store, Middlewares… và viết khá nhiều code chỉ để quản lý một phần state nhỏ.
- Hệ Thống Global Store Tập Trung: Việc có một Store duy nhất chứa toàn bộ state đôi khi có thể trở thành điểm nghẽn hiệu năng hoặc khó khăn trong việc chia sẻ state giữa các phần không liên quan trực tiếp của ứng dụng.
- Không Hoàn Toàn “React Native”: Redux là một thư viện quản lý state độc lập, có thể dùng với các UI library khác. Để dùng trong React, cần kết hợp với
react-redux
. Các giải pháp mới thường được thiết kế “React-first”, tận dụng tối đa Hooks và ngữ cảnh của React. - Thiếu Tối Ưu cho Concurrent Mode: Redux (trước đây) không được thiết kế để tận dụng các tính năng mới của React như Concurrent Mode một cách tự nhiên. Các thư viện mới có thể được xây dựng với những khả năng này ngay từ đầu.
Các lựa chọn thay thế như Recoil và Zustand ra đời nhằm cung cấp một cách quản lý state hiệu quả, đơn giản hơn, và phù hợp hơn với phong cách phát triển React hiện đại sử dụng Hooks.
Recoil: Quản Lý State Với Đồ Thị State (State Graph)
Recoil là thư viện quản lý state mã nguồn mở cho React, được phát triển bởi Facebook (nay là Meta). Nó được thiết kế để cung cấp một API tối thiểu, React-centric, cho phép quản lý state phân tán và tính toán state dẫn xuất một cách hiệu quả, đặc biệt phù hợp với Concurrent Mode.
Các Khái Niệm Cốt Lõi của Recoil
Recoil xây dựng mô hình state của ứng dụng dưới dạng đồ thị (graph) với hai loại node chính:
-
Atoms: Các đơn vị state nhỏ nhất, có thể cập nhật và đăng ký theo dõi bởi các component. Mỗi atom là một “nguồn sự thật” (source of truth) riêng lẻ. Các component đăng ký sử dụng một atom sẽ được render lại khi giá trị của atom đó thay đổi.
import { atom } from 'recoil'; const textState = atom({ key: 'textState', // Unique ID (with respect to other atoms/selectors) default: '', // Default value (initial state) }); const fontSizeState = atom({ key: 'fontSizeState', default: 14, });
-
Selectors: Các đơn vị state “dẫn xuất” (derived state) hoặc tính toán. Một selector lấy giá trị từ các atom khác (hoặc các selector khác) và biến đổi chúng thành một giá trị mới. Khi các atom hoặc selector mà một selector phụ thuộc vào thay đổi, selector đó sẽ được tính toán lại. Selectors cũng có thể bất đồng bộ (asynchronous), cho phép bạn dễ dàng xử lý data fetching.
import { selector } from 'recoil'; import { textState } from './atoms'; // Import the atom const charCountState = selector({ key: 'charCountState', // Unique ID get: ({ get }) => { const text = get(textState); // Get value from textState atom return text.length; // Calculate derived state }, });
Mô hình atom/selector này cho phép bạn chia nhỏ state thành các phần độc lập, sau đó kết hợp và biến đổi chúng thông qua selectors, tạo thành một đồ thị state linh hoạt.
Cách Sử Dụng Recoil
Để sử dụng Recoil, bạn cần bọc ứng dụng của mình trong <RecoilRoot>
:
import React from 'react';
import ReactDOM from 'react-dom';
import { RecoilRoot } from 'recoil';
import App from './App';
ReactDOM.render(
<RecoilRoot>
<App />
</RecoilRoot>,
document.getElementById('root')
);
Sau đó, trong các component, bạn sử dụng các Hooks do Recoil cung cấp để tương tác với atoms và selectors:
useRecoilState(atom)
: Tương tựuseState
, trả về một mảng[value, setter]
cho một atom.useRecoilValue(atomOrSelector)
: Trả về giá trị của một atom hoặc selector (chỉ để đọc).useSetRecoilState(atom)
: Trả về chỉ hàm setter cho một atom (để cập nhật mà không cần re-render component khi giá trị atom thay đổi, hữu ích cho các event handler).useResetRecoilState(atom)
: Trả về hàm để reset atom về giá trị mặc định.
Ví dụ sử dụng atom:
import React from 'react';
import { useRecoilState } from 'recoil';
import { textState } from './atoms'; // Import the atom
function TextInput() {
const [text, setText] = useRecoilState(textState);
const onChange = (event) => {
setText(event.target.value);
};
return (
<div>
<input type="text" value={text} onChange={onChange} />
<br />
Echo: {text}
</div>
);
}
export default TextInput;
Ví dụ sử dụng selector:
import React from 'react';
import { useRecoilValue } from 'recoil';
import { charCountState } from './selectors'; // Import the selector
function CharacterCounter() {
const count = useRecoilValue(charCountState); // Get derived value
return (
<div>
Character Count: {count}
</div>
);
}
export default CharacterCounter;
Ưu Điểm của Recoil
- React-centric và API Tối Giản: Được xây dựng với React trong tâm trí, API rất giống với
useState
, dễ học nếu bạn đã quen với Hooks. - Quản Lý State Phân Tán: Atoms cho phép bạn chia nhỏ state, tránh monolithic store.
- State Dẫn Xuất Mạnh Mẽ (Selectors): selectors là cách hiệu quả và tự nhiên để tính toán state dựa trên các state khác, bao gồm cả xử lý bất đồng bộ.
- Tương Thích Concurrent Mode: Được thiết kế để hoạt động tốt với các tính năng mới của React, mang lại tiềm năng về hiệu năng.
- Ít Boilerplate: So với Redux truyền thống, Recoil yêu cầu ít code cấu hình hơn.
Nhược Điểm của Recoil
- Masih dianggap là ‘Experimental’ (Tạm thời): Mặc dù được sử dụng trong sản phẩm của Meta, Recoil vẫn được đánh dấu là “experimental” trong một thời gian dài, gây ra sự ngần ngại cho một số dự án production quan trọng. Tuy nhiên, tình hình này đang dần thay đổi khi thư viện ngày càng ổn định.
- Hệ Sinh Thái Còn Non Trẻ: So với Redux, hệ sinh thái các middleware, devtools, và tài liệu có thể chưa phong phú bằng.
Zustand: Đơn Giản Đến Tối Đa
Zustand (nghĩa là “state” trong tiếng Đức) là một thư viện quản lý state nhỏ, nhanh và có thể mở rộng cho React, được phát triển bởi đội ngũ Poimandres (đứng sau react-three-fiber). Điểm nổi bật của Zustand là sự đơn giản và API tối thiểu, hoàn toàn dựa trên Hooks.
Cách Hoạt Động của Zustand
Thay vì các khái niệm như Reducers, Actions, hay thậm chí Atoms/Selectors, Zustand chỉ có khái niệm về một “store”. Store trong Zustand là một hàm đơn giản trả về state và các hàm để cập nhật state đó. Hooks được sử dụng để tương tác trực tiếp với store này từ component.
Việc tạo store với Zustand cực kỳ đơn giản:
import { create } from 'zustand';
const useCounterStore = create((set) => ({
count: 0, // initial state
increaseCount: () => set((state) => ({ count: state.count + 1 })), // action to update state
decreaseCount: () => set((state) => ({ count: state.count - 1 })),
resetCount: () => set({ count: 0 }),
}));
Hàm create
nhận một hàm callback, hàm này nhận đối số là hàm set
(để cập nhật state) và trả về object chứa state và các hành động (actions). Các hành động này là các hàm thông thường gọi đến set
.
Cách Sử Dụng Zustand
Sau khi tạo store, bạn sử dụng nó trong các component thông qua hook được trả về bởi create
(ở ví dụ trên là useCounterStore
):
import React from 'react';
import useCounterStore from './store'; // Import your store hook
function Counter() {
// Get state and actions directly from the hook
const count = useCounterStore((state) => state.count);
const increaseCount = useCounterStore((state) => state.increaseCount);
const decreaseCount = useCounterStore((state) => state.decreaseCount);
const resetCount = useCounterStore((state) => state.resetCount);
// Or get multiple state/actions at once
// const { count, increaseCount, decreaseCount } = useCounterStore();
return (
<div>
<h3>Count: {count}</h3>
<button onClick={increaseCount}>Increase</button>
<button onClick={decreaseCount}>Decrease</button>
<button onClick={resetCount}>Reset</button>
</div>
);
}
export default Counter;
Zustand sử dụng một cơ chế publish-subscribe nội bộ, chỉ re-render component khi phần state mà component đang “đăng ký” (select) thay đổi. Điều này giúp tối ưu hiệu năng mà không cần Provider bọc ngoài toàn bộ ứng dụng như Redux hay Recoil (mặc dù bạn vẫn có thể sử dụng Context nếu cần truyền store xuống sâu hơn hoặc cho server-side rendering).
Ưu Điểm của Zustand
- Cực Kỳ Đơn Giản và API Tối Thiểu: Đây là ưu điểm lớn nhất của Zustand. Không có khái niệm phức tạp, chỉ là hàm
create
và các hooks. - Zero Boilerplate: Tạo store và sử dụng rất nhanh chóng.
- Nhỏ Gọn: Kích thước bundle của Zustand rất nhỏ.
- Không Cần Context Provider (Trong Hầu Hết Trường Hợp): Điều này đơn giản hóa cây component và cấu hình ban đầu.
- Không Chỉ Dành Cho React: Mặc dù phổ biến nhất với React, lõi của Zustand là độc lập với UI, có thể dùng ở môi trường JavaScript/TypeScript khác.
- Hooks-Based: Tận dụng hoàn toàn sức mạnh và sự tiện lợi của React Hooks.
Nhược Điểm của Zustand
- Ít Cấu Trúc Hơn: Sự đơn giản cũng có nghĩa là Zustand ít “opinionated” hơn Redux. Đối với các ứng dụng rất lớn, việc quản lý và cấu trúc store phức tạp có thể đòi hỏi sự kỷ luật cao hơn từ phía developer.
- Hệ Sinh Thái: Tương tự Recoil, hệ sinh thái Addons (như persist state, devtools tích hợp sâu) đang phát triển nhưng chưa đồ sộ bằng Redux.
So Sánh Recoil, Zustand và Redux
Để có cái nhìn tổng quan hơn, hãy cùng xem xét một bảng so sánh các điểm chính:
Tiêu Chí | Recoil | Zustand | Redux (với Redux Toolkit) |
---|---|---|---|
Nhà Phát Triển/Bảo Trì | Meta (Facebook) | Đội ngũ Poimandres | Cộng đồng (Mark Erikson dẫn đầu) |
Triết Lý | State Graph (Atoms & Selectors), React-centric, Tối ưu cho Concurrent Mode | Đơn giản tối đa, Hooks-based, Ít Boilerplate, Linh hoạt | Single Source of Truth, Predictable State Changes, Middleware, Độc lập UI |
Boilerplate | Ít hơn Redux truyền thống, cần <RecoilRoot> | Rất ít (Zero Boilerplate), không cần Context Provider trong đa số trường hợp | Giảm nhiều với Redux Toolkit, vẫn cần setup Store/Slices, Provider |
Độ Phức Tạp | Concepts mới: Atoms, Selectors. API Hooks quen thuộc. | Rất đơn giản, chỉ là hooks và hàm tạo store. | Actions, Reducers, Store, Middleware… có thể là steep learning curve ban đầu. |
Quản Lý State Dẫn Xuất | Selectors là công cụ mạnh mẽ và tích hợp sẵn. | Phải tự định nghĩa logic tính toán trong store hoặc selector function thủ công. | Selectors (reselect library) là pattern phổ biến, cần setup thêm. |
Xử lý Bất Đồng Bộ | Selectors hỗ trợ bất đồng bộ tự nhiên. | Xử lý trong các action của store (async/await). | Redux Thunk/Saga là middleware phổ biến để quản lý side effects. |
Tích hợp với React | Thiết kế cho React, Hooks-centric, tương thích tốt với Concurrent Mode. | Thiết kế cho React (chủ yếu), Hooks-based, không cần Provider. | Cần react-redux để kết nối React với Redux store. |
Hệ Sinh Thái | Còn non trẻ, đang phát triển. | Đang phát triển, cộng đồng Poimandres tích cực. | Rất lớn mạnh, nhiều tools, middleware, extensions. |
Kích Thước Bundle | Nhỏ đến trung bình. | Rất nhỏ. | Trung bình (Redux core + RTK + react-redux). |
Phù Hợp Với | Ứng dụng React có quy mô trung bình đến lớn, cần quản lý state phân tán và state dẫn xuất phức tạp, muốn tận dụng các tính năng React mới. | Ứng dụng React có quy mô nhỏ đến trung bình, cần giải pháp đơn giản, nhanh gọn, ít boilerplate, hoặc các dự án non-React. | Ứng dụng có quy mô lớn, cần cấu trúc chặt chẽ, muốn hệ sinh thái công cụ đồ sộ, team đã quen thuộc với Flux/Redux. |
Chọn Lựa Công Cụ Nào?
Việc lựa chọn giữa Redux, Recoil và Zustand phụ thuộc vào nhiều yếu tố:
- Kinh Nghiệm của Team: Nếu team của bạn đã rất quen thuộc và thoải mái với Redux Toolkit, việc tiếp tục sử dụng nó có thể là lựa chọn an toàn và hiệu quả.
- Quy Mô và Độ Phức Tạp của Ứng Dụng:
- Đối với các ứng dụng nhỏ hoặc các phần state cục bộ lớn hơn
useState
/useReducer
nhưng chưa đến mức toàn cầu, Zustand có thể là lựa chọn tuyệt vời vì sự đơn giản và tốc độ. - Đối với các ứng dụng có quy mô trung bình đến lớn, nơi bạn cần quản lý nhiều phần state độc lập và có nhiều state dẫn xuất, Recoil với mô hình Atoms/Selectors của nó rất phù hợp. Nó cũng là lựa chọn hứa hẹn cho tương lai của React với Concurrent Mode.
- Đối với các ứng dụng rất lớn, phức tạp, đòi hỏi quản lý side effects mạnh mẽ, debug tools tiên tiến và cấu trúc chặt chẽ, Redux Toolkit vẫn là một ứng cử viên sáng giá với hệ sinh thái trưởng thành của nó.
- Đối với các ứng dụng nhỏ hoặc các phần state cục bộ lớn hơn
- Yêu cầu Cụ Thể: Nếu bạn cần một thư viện cực kỳ nhỏ gọn và không quan tâm nhiều đến cấu trúc chặt chẽ, Zustand vượt trội. Nếu bạn cần xử lý bất đồng bộ cho state dẫn xuất một cách tự nhiên, Recoil’s selectors là điểm cộng lớn.
Đừng quên rằng bạn hoàn toàn có thể bắt đầu với các giải pháp tích hợp sẵn của React như useState
, useReducer
và useContext
trước khi chuyển sang các thư viện bên ngoài khi ứng dụng lớn dần. Việc chọn công cụ quản lý state nào là một quyết định quan trọng trên React Roadmap.
Kết Luận
Recoil và Zustand đại diện cho làn sóng các thư viện quản lý state hiện đại trong hệ sinh thái React. Chúng mang đến những cách tiếp cận mới, đơn giản hơn, ít boilerplate hơn và tận dụng tốt hơn các tính năng của React Hooks so với Redux truyền thống.
- Recoil cung cấp mô hình state graph mạnh mẽ với Atoms và Selectors, phù hợp cho các ứng dụng cần state phân tán và state dẫn xuất phức tạp.
- Zustand nổi bật với sự đơn giản tối đa, API hooks trực quan và kích thước nhỏ gọn, là lựa chọn tuyệt vời cho các ứng dụng nhỏ đến trung bình hoặc khi bạn muốn một giải pháp ít rườm rà nhất.
Không có “người thắng cuộc” tuyệt đối. Lựa chọn tốt nhất là thứ phù hợp nhất với nhu cầu cụ thể của dự án, kinh nghiệm của đội ngũ và sở thích cá nhân. Quan trọng là bạn hiểu được các lựa chọn này tồn tại, cách chúng hoạt động và ưu nhược điểm của từng loại để đưa ra quyết định sáng suốt.
Hãy thử nghiệm cả Recoil và Zustand trong các dự án nhỏ để cảm nhận sự khác biệt và xem cái nào phù hợp với phong cách làm việc của bạn nhất. Việc nắm vững các công cụ quản lý state hiện đại là một bước tiến lớn trên con đường trở thành một developer React chuyên nghiệp.
Trong bài viết tiếp theo, chúng ta có thể sẽ đi sâu vào một chủ đề khác không kém phần quan trọng trong React Roadmap. Hãy tiếp tục theo dõi nhé!