
Mình đã thay Redux bằng React Context và hối hận: Bài học rút ra khi mở rộng một React webapp cỡ "bự"
Có một thời điểm mình tin rằng React Context có thể thay thế hoàn toàn Redux. Ít boilerplate hơn, ít file hơn và mọi thứ trông đơn giản hơn rất nhiều. Nhưng khi ứng dụng phát triển, những vấn đề về re-render, global state management và khả năng mở rộng bắt đầu xuất hiện. Đây là câu chuyện về lý do mình thay Redux bằng React Context, những sai lầm mình mắc phải và bài học rút ra sau đó.
Đã có một thời điểm mình quyết định loại bỏ Redux khỏi một dự án.
Lý do rất đơn giản.
Redux lúc đó có cảm giác hơi nặng.
Mỗi khi muốn thêm một phần state mới, mình lại phải tạo actions, reducers, slices, selectors và khá nhiều file chỉ để lưu vài giá trị. Trong khi đó, React đã có sẵn Context.
Mình bắt đầu tự hỏi:
Tại sao phải dùng Redux khi React đã cho mình Context miễn phí?
Ban đầu, việc thay Redux bằng React Context có vẻ là một quyết định rất đúng đắn.
Codebase gọn hơn.
Ít boilerplate hơn.
State management trông đơn giản hơn.
Và thành thật mà nói, nó có cảm giác "React" hơn rất nhiều.
Trong một khoảng thời gian, mình thực sự tin rằng mình đã tìm được một giải pháp tốt hơn.
Rồi ứng dụng bắt đầu lớn lên.
Thêm tính năng mới.
Thêm state cần được chia sẻ.
Thêm component phụ thuộc vào cùng một nguồn dữ liệu.
Và dần dần, các vấn đề bắt đầu xuất hiện.
Không phải những vấn đề nghiêm trọng.
Không phải kiểu bug làm production sập ngay lập tức.
Mà là những vấn đề âm thầm tích tụ cho tới khi một ngày bạn nhận ra có điều gì đó không ổn.
Bài viết này là những gì mình học được sau khi cố gắng thay thế Redux hoàn toàn bằng React Context, vì sao quyết định đó ban đầu trông rất hợp lý và tại sao cuối cùng mình lại hối hận.
React Context vs Redux
Trước khi đi sâu hơn, mình nghĩ cần làm rõ một điều.
React Context và Redux không thực sự là đối thủ trực tiếp của nhau.
React Context chủ yếu được tạo ra để chia sẻ dữ liệu xuyên suốt component tree.
Trong khi đó, Redux được thiết kế để quản lý state của ứng dụng ở quy mô lớn.
Thoạt nhìn chúng có vẻ giải quyết cùng một vấn đề. Và đó cũng là lý do rất nhiều developer, bao gồm cả mình, từng cố gắng thay Redux bằng Context.
Sai lầm nằm ở chỗ nghĩ rằng chúng hoàn toàn giống nhau.
Trên thực tế, hai công cụ này có phần giao nhau, nhưng điểm mạnh của chúng bắt đầu khác biệt rõ ràng khi ứng dụng ngày càng phát triển.
Vì sao React Context trông giống giải pháp hoàn hảo
Khi developer phàn nàn về Redux, thứ họ thường phàn nàn là độ phức tạp.
Mặc dù Redux Toolkit đã giúp Redux dễ dùng hơn rất nhiều so với trước đây, nó vẫn yêu cầu một số khái niệm mà React Context không cần.
Với Context, việc bắt đầu gần như đơn giản nhất có thể.
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
return (
<AuthContext.Provider value={{ user, setUser }}>
{children}
</AuthContext.Provider>
);
}Chỉ vậy thôi.
Không cần cấu hình store.
Không cần slices.
Không cần actions.
Không cần reducers.
Chỉ có React.
Với những dự án nhỏ, điều này thực sự rất hấp dẫn.
Sự đơn giản đó rất khó để bỏ qua.
Và cũng chính vì thế mà rất nhiều developer, bao gồm cả mình, bắt đầu tự hỏi liệu Redux có còn cần thiết nữa hay không.
Mọi thứ hoạt động hoàn hảo ở giai đoạn đầu
Điều thú vị là Context thực sự hoạt động rất tốt trong thời gian đầu.
Ứng dụng của mình khi đó chưa quá lớn.
Global state chỉ bao gồm một vài thứ như:
- authentication
- theme settings
- notifications
Context xử lý tất cả những thứ đó rất ổn.
Không có vấn đề về performance.
Không có vấn đề về kiến trúc.
Không có dấu hiệu nào cho thấy quyết định này là sai lầm.
Những bài viết với tiêu đề kiểu:
"You don't need Redux anymore"
lúc đó nghe hoàn toàn hợp lý.
Trong một thời gian dài, mình thật sự tin rằng Redux đang dần trở nên không cần thiết.
Rồi dự án tiếp tục phát triển.
Ứng dụng lớn hơn mình nghĩ rất nhiều
Khi có thêm tính năng mới, lượng state cần chia sẻ cũng tăng lên.
Từ vài Context ban đầu, ứng dụng dần xuất hiện thêm:
- authentication
- user profile
- search filters
- dashboard data
- messaging
- notifications
- settings
Mỗi feature lại mang theo một Context mới.
Mỗi Context lại có thêm nhiều consumer.
Và mỗi consumer lại khiến nhiều component phụ thuộc hơn vào state dùng chung.
Ở thời điểm đó, mọi thứ vẫn có vẻ ổn.
Ứng dụng vẫn chạy.
Người dùng vẫn hài lòng.
Không có lỗi nào rõ ràng xuất hiện.
Nhưng việc phát triển bắt đầu trở nên khó chịu hơn trước.
Những vấn đề về hiệu năng của React Context
Một điều mình không thực sự hiểu ở giai đoạn đầu là cách hiệu năng của React Context thay đổi khi ứng dụng ngày càng lớn.
Trong các dự án nhỏ, Context có cảm giác rất nhanh.
Nhưng trong những dự án lớn hơn, vấn đề re-render của React Context bắt đầu trở nên khó kiểm soát.
Một thay đổi nhỏ trong provider có thể khiến rất nhiều consumer re-render, ngay cả khi chúng không sử dụng trực tiếp dữ liệu vừa thay đổi.
Đây là vấn đề đầu tiên liên quan tới khả năng mở rộng mà mình gặp phải sau khi thay Redux bằng Context.
Vấn đề re-render xuất hiện rõ ràng hơn mình nghĩ
Nhiều người biết rằng Context có thể gây re-render, nhưng mình nghĩ không phải ai cũng nhận ra nó có thể trở thành vấn đề nhanh như thế nào trong những ứng dụng lớn.
Ví dụ:
const value = {
user,
setUser,
};
<AuthContext.Provider value={value}>Mỗi khi provider re-render, tất cả consumer sử dụng Context đó đều có khả năng re-render theo.
Ngay cả khi component chỉ sử dụng một phần rất nhỏ của dữ liệu.
Ngay cả khi dữ liệu mà component cần không hề thay đổi.
Ngay cả khi component chỉ dùng một function trong Context.
Khi ứng dụng còn nhỏ, chuyện này gần như không đáng chú ý.
Nhưng khi số lượng component tăng lên, tác động của nó bắt đầu rõ ràng hơn rất nhiều.
Một thay đổi state đơn giản đôi khi kéo theo hàng loạt lần render mà mình không hề mong muốn.
React DevTools khiến mình bất ngờ
Lần đầu tiên mình dành thời gian nghiêm túc để dùng React DevTools Profiler, mình thực sự bất ngờ.
Ban đầu mình nghĩ sẽ chỉ tìm thấy vài component hoạt động chưa tối ưu.
Nhưng thứ mình thấy lại là cả một chuỗi component render liên tục.
Một thay đổi nhỏ có thể khiến hàng chục component render lại.
Không phải vì dữ liệu của chúng thay đổi.
Không phải vì UI thay đổi.
Chỉ đơn giản vì Context thay đổi.
Đó là lúc mình nhận ra:
Context rất đơn giản, nhưng nó không miễn phí.
Cái giá phải trả chỉ thực sự lộ ra khi ứng dụng bắt đầu lớn dần.
Context không thực sự là một thư viện quản lý state
Đây có lẽ là hiểu lầm lớn nhất của mình.
Trong một thời gian dài, mình coi Context là phiên bản thay thế của Redux.
Lúc đó, mình đang xem React Context như một giải pháp hoàn chỉnh cho bài toán global state management.
Nhìn lại, đó có lẽ là sai lầm lớn nhất.
Context chủ yếu là một cơ chế để chia sẻ dữ liệu trong component tree.
Redux được thiết kế để quản lý state của ứng dụng ở quy mô lớn.
Dùng Context để chia sẻ một vài giá trị chung là rất hợp lý.
Dùng Context như giải pháp state management chính cho toàn bộ ứng dụng lại là một câu chuyện khác.
Redux Toolkit vs React Context
Hiện tại, khi bắt đầu một dự án mới, mình không còn mặc định chọn Redux Toolkit hoặc React Context nữa.
Thay vào đó, mình đánh giá:
- quy mô của ứng dụng
- độ phức tạp của state
- tần suất dữ liệu thay đổi
- số lượng developer tham gia dự án
Với các ứng dụng đơn giản, Context thường là đủ.
Với những ứng dụng lớn hơn, Redux Toolkit vẫn mang lại trải nghiệm phát triển tốt hơn đáng kể.
Điều mình đánh giá cao khi quay lại Redux
Cuối cùng, mình chuyển một phần state trở lại Redux Toolkit.
Điều thú vị là thứ mình đánh giá cao nhất không phải performance.
Mà là tính dễ dự đoán.
Với Redux, component chỉ subscribe đúng phần state mà nó cần.
const user = useSelector(
state => state.auth.user
);Nếu notifications thay đổi, component này không bị ảnh hưởng.
Nếu settings thay đổi, component này cũng không bị ảnh hưởng.
Nếu dashboard data thay đổi, nó vẫn không bị ảnh hưởng.
Mối quan hệ giữa state thay đổi và component update trở nên dễ hiểu hơn rất nhiều.
Khi ứng dụng phát triển lớn hơn, điều đó có giá trị cực kỳ lớn.
Khi nào nên dùng React Context?
Theo trải nghiệm của mình, React Context hoạt động tốt nhất cho:
- authentication
- theme switching
- localization
- feature flags
Đây là những dữ liệu không thay đổi liên tục và thường không có logic quá phức tạp.
Khi nào nên dùng Redux?
Redux bắt đầu trở nên hợp lý hơn khi:
- nhiều feature cùng chia sẻ một state
- state thay đổi thường xuyên
- ứng dụng có realtime data
- nhiều developer cùng làm việc trên codebase
- việc debug state bắt đầu trở nên khó khăn
React Context vs Redux: Quy tắc mình thường áp dụng
| Tình huống | React Context | Redux |
|---|---|---|
| Ứng dụng nhỏ | ✅ | ❌ |
| Ứng dụng vừa | ✅ | ⚠️ |
| Ứng dụng lớn | ⚠️ | ✅ |
| Ứng dụng realtime | ❌ | ✅ |
| Authentication | ✅ | ⚠️ |
| State phức tạp | ❌ | ✅ |
Liệu mình còn dùng React Context nữa không?
Chắc chắn là có.
Thực tế mình vẫn dùng Context rất thường xuyên.
Chỉ là không dùng nó cho mọi thứ nữa.
Hiện tại mình thấy Context rất phù hợp cho:
- authentication
- theme
- localization
- feature flags
Còn với những bài toán state management lớn hơn, mình thường cân nhắc:
- Redux Toolkit
- Zustand
- Jotai
Tùy thuộc vào từng dự án.
FAQ
React Context có tốt hơn Redux không?
Không hẳn.
React Context đơn giản hơn và được tích hợp sẵn trong React, nhưng Redux có khả năng mở rộng tốt hơn cho các ứng dụng lớn với state phức tạp.
React Context có gây re-render không?
Có.
Khi giá trị trong provider thay đổi, tất cả consumer đều có khả năng re-render.
Đây là một trong những vấn đề hiệu năng phổ biến nhất của React Context.
React Context có thể thay thế Redux không?
Với các ứng dụng nhỏ và vừa thì hoàn toàn có thể.
Tuy nhiên, với các ứng dụng lớn, câu trả lời sẽ phụ thuộc vào độ phức tạp của bài toán global state management.
Điều mình học được
Sai lầm lớn nhất không phải là sử dụng React Context.
Sai lầm lớn nhất là nghĩ rằng React Context và Redux có thể thay thế hoàn toàn cho nhau.
Với ứng dụng nhỏ, Context hoàn toàn có thể thay thế Redux.
Với ứng dụng cỡ trung, điều đó vẫn có thể hoạt động rất tốt.
Nhưng khi ứng dụng bắt đầu lớn lên, bài toán state management cũng thay đổi.
Lúc đó vấn đề không còn là viết ít code hơn nữa.
Mà là khả năng mở rộng.
Khả năng dự đoán.
Performance.
Và trải nghiệm phát triển lâu dài.
Đó chính là những vấn đề Redux được tạo ra để giải quyết.
Sau khi cố gắng thay thế Redux hoàn toàn bằng React Context, mình cuối cùng cũng hiểu vì sao Redux vẫn tồn tại sau ngần ấy năm.
Không phải vì developer thích viết thêm code.
Mà vì những ứng dụng lớn luôn tạo ra những vấn đề mà các giải pháp đơn giản cuối cùng sẽ phải đối mặt.
Và đáng tiếc là mình chỉ hiểu điều đó sau khi tự trải nghiệm nó.
Thẻ
Bài viết liên quan

React WebRTC: Cách Fix Lỗi Failed to set remote answer sdp
Mình đã mất khá nhiều thời gian để debug lỗi InvalidStateError: Failed to set remote answer sdp trong một ứng dụng React WebRTC sử dụng Simple-Peer và Socket.IO. Vấn đề thực sự không nằm ở SDP, mà đến từ duplicated signaling events, signaling state sai và cách React xử lý lifecycle. Đây là nguyên nhân thật sự và cách fix đã hoạt động với mình.

Cách dùng Google Maps hiệu quả hơn với các tính năng ít người biết
Phần lớn mọi người chỉ dùng Google Maps để tìm đường từ điểm A tới điểm B. Nhưng sau nhiều năm sử dụng gần như mỗi ngày, mình nhận ra ứng dụng này có rất nhiều tính năng nhỏ nhưng cực kỳ hữu ích mà đa số người dùng gần như bỏ qua. Từ việc lưu chỗ đậu xe, tải offline maps cho tới dùng Street View trước khi tới nơi lạ, đây là những Google Maps hacks đã âm thầm giúp mình tiết kiệm thời gian, đỡ stress hơn khi di chuyển và khiến trải nghiệm đi lại hằng ngày thoải mái hơn rất nhiều.

Những Google AI Pro hacks đã thay đổi workflow của mình
Dạo gần đây mình thấy khá nhiều người đã claim được gói student của Google AI Pro, nhưng đa số vẫn đang dùng nó giống như bản free — mở AI lên hỏi vài câu cơ bản, lâu lâu nhờ tóm tắt tài liệu hoặc viết lại vài đoạn văn. Trong khi sau một thời gian dùng thật cho việc viết content, research và học tập, mình thấy thứ đáng giá nhất của gói Pro lại không nằm ở chuyện “AI thông minh hơn”, mà là nó giúp workflow hằng ngày trở nên nhanh và liền mạch hơn rất nhiều.