Redux là một thư viện quản lý state (trạng thái) vô cùng phổ biến trong hệ sinh thái React. Nó cho phép chúng ta có một global store để lưu trữ dữ liệu bất biến, giải quyết được một loạt các vấn đề về state khi ứng dụng có các component chồng chéo nhau.
Chúng ta cứ hiểu đơn giản global store là nơi lưu trữ chung tách biệt với ứng dụng. Khi bất kể component nào cần tới thì cứ vào đó mà lấy ra.
Nhưng câu hỏi đặt ra là: Liệu chúng ta có thực sự cần một global store? Ứng dụng của chúng ta có thực sự phức tạp hay chúng ta quá lạm dụng redux?
Bài viết dưới đây thể hiện quan điểm của tác giả Gabriel Abud (Software Engineer tại Tempo Automation) về việc tại sao chúng ta không cần thiết phải sử dụng Redux.
Nội dung chính của bài viết
Vấn đề nảy sinh từ ứng dụng Single Page (SPA)
Sự ra đời của ứng dụng Single Page (SPA) đã làm thay đổi hẳn cách chúng ta xây dựng ứng dụng web, cũng như trải nghiệm người dùng. Việc tách biệt giữa back-end và front-end cho phép người phát triển chuyên môn hóa hơn, tập trung vào thế mạnh của họ.
Tuy nhiên, ứng dụng Single Page lại cũng có những vấn đề phức tạp kéo theo, điển hiển trong số đó là việc quản lý trạng thái của ứng dụng. Nếu như trước đây, với các ứng dụng web cổ điển, mỗi lần cần thay đổi dữ liệu là chúng ta load lại toàn bộ trang là xong. Nhưng với ứng dụng Single Page thì không như vậy, nó chỉ load lại phần nào có thay đổi mà thôi.
Việc lấy dữ liệu bất đồng bộ giờ đây cũng khác trước, dữ liệu cần phải lưu ở cả hai nơi:
- Lưu ở phía front-end
- Và lưu ở back-end
Chúng ta cần phải suy nghĩ làm sao có thể lưu dữ liệu ở một nơi nào đó, mà có thể sẵn sàng sử dụng cho tất cả component, đồng thời lưu cache để giảm độ trễ của mạng.
Từ đó, phần lớn thời gian khi phát triển front-end là dành cho việc quản lý cái global store đó, sao cho không bị lỗi state, dữ liệu được đồng bộ…
Redux không phải là một giải pháp tạo cache
Có một tình trạng phổ biến mà nhiều bạn sử dụng Redux hay các thư viện quản lý state khác đó là coi nó như là bộ nhớ cache của ứng dụng. Bạn đều đặn lấy dữ liệu hay đẩy dữ liệu vào global store.
Vô hình chung, điều này khiến cho Redux làm việc quá nhiều, sử dụng nó để giải quyết bài toán mà sinh thời nó không được tạo ra để làm điều đó.
Còn một điều quan trọng nữa, state của front-end và back-end không bao giờ thực sự là đồng bộ với nhau. Đây cũng là một nhược điểm của mô hình server – client và tại sao chúng ta cần một bộ nhớ cache.
Ranh giới giữa back-end và front-end sẽ bị xóa mờ nếu bạn có ý định tạo lại cơ sở dữ liệu phía front-end. Thông thường, với lập trình viên front-end, họ không cần biết quá nhiều về cơ sở dữ liệu, về mối quan hệ giữa các bảng trong db, chuẩn hóa dữ liệu… để xây dựng lên giao diện người dùng.
Những công việc liên quan đến quản lý DB sẽ do back-end developer đảm nhận. Họ sẽ cung cấp API để front-end developer sử dụng.
Điều gì xảy ra nếu chúng ta không quản lý back-end state trên front-end, thay vào đó, coi nó như một bộ nhớ cache chỉ cần cập nhật định kỳ là được?
Với cách làm này, chúng ta coi front-end đơn giản là một layer hiển thị thông tin từ bộ nhớ cache. Lúc này, mã nguồn sẽ đơn giản hơn, dễ tiếp cận hơn với các front-end developer.
Giải pháp tiếp cận backend state đơn giản hơn
Có một vài thư viện thay thế redux để lưu trữ backend state đơn giản và hiệu quả hơn rất nhiều.
React Query
Mình đã sử dụng thư viện này được vài tháng, cho cả dự án cá nhân và công ty. Đây là một thư viện với một API rất đơn giản, và có hooks để quản lý query (fetching data) hay mutations (thay đổi data).
Từ lúc chuyển sang dùng React Query, mình cảm thấy năng suất làm việc cải thiện rõ rệt. Mình sẽ toàn tâm toàn ý tập trung vào UI/UX của ứng dụng thay vì cứ phải lo nghĩ về quản lý state.
Để chứng minh cho điều mình nói ở trên, chúng ta sẽ cùng nhau xem xét một ví dụ. Trong ví dụ này, chúng ta sẽ hiển thị danh sách TODOs được lấy từ server sử dụng vanilla JS, React Hooks, và axios.
Giải pháp sử dụng redux
import React, { useEffect } from "react"; import { useSelector, useDispatch } from "react-redux"; import axios from 'axios'; const SET_TODOS = "SET_TODOS"; export const rootReducer = (state = { todos: [] }, action) => { switch (action.type) { case SET_TODOS: return { ...state, todos: action.payload }; default: return state; } }; export const App = () => { const todos = useSelector((state) => state.todos); const dispatch = useDispatch(); useEffect(() => { const fetchPosts = async () => { const { data } = await axios.get("/api/todos"); dispatch({ type: SET_TODOS, payload: data} ); }; fetchPosts(); }, []); return ( <ul>{todos.length > 0 && todos.map((todo) => <li>{todo.text}</li>)}</ul> ); };
Đoạn code trên chúng ta chỉ đơn giản là load dữ liệu và lưu vào trong global store. Chúng ta còn chưa thực hiện xử lý refresh data, cache hay validate dữ liệu.
Giải pháp sử dụng React Query
import React from "react"; import { useQuery } from "react-query"; import axios from "axios"; const fetchTodos = () => { const { data } = axios.get("/api/todos"); return data; }; const App = () => { const { data } = useQuery("todos", fetchTodos); return data ? ( <ul>{data.length > 0 && data.map((todo) => <li>{todo.text}</li>)}</ul> ) : null; };
Bạn thấy chưa? Code ngắn hơn hẳn, cá nhân mình cũng thấy dễ hiểu hơn.
Muốn tìm hiểu nhiều hơn về React Query? Bạn có thể tham khảo ở đây.
SWR
Về cơ bản, SWR khá giống với React Query. Cả hai thư viện này được phát triển cùng thời gian, có đôi chút ảnh hưởng lẫn nhau. Do đó, nếu bạn không thích React Query thì có thể sử dụng thằng này thay thế cũng được.
Apollo Client
Cả hai thư viện SWR và React Query tập trung thế mạnh vào REST API, nhưng nếu bạn cần một thư viện giống như thế mà lại dành cho GraphQL thì Apollo Client là ứng cử viên đáp ứng được trọn vẹn.
Cú pháp của Apollo Client gần giống với React Query nên bạn sẽ dễ dàng học và tìm hiểu cách sử dụng thư viện này.
Vậy còn front-end state thì sao?
Khi bạn sử dụng các thư viện mà mình giới thiệu ở trên, bạn sẽ cảm thấy redux hơi thừa thãi. Khi phần data fetching/caching đã được xử lý thì sẽ có rất ít state chung để bạn cần phải xử lý, quản lý trên giao diện người dùng. Nếu muốn quản lý số state đó, bạn có thể sử dụng Context hoặc kết hợp hai hooks: useContext() + useReducer()
.
😄 Đọc luôn cũng được: Quản lý State với React Hooks – Không cần Redux hay Context API
Hoặc sử dụng ngày hook useState()
cũng được.
// clean, beautiful, and simple const [state, setState] = useState();
Trên đây là quan điểm cá nhân của tác giả Gabriel Abud về việc sử dụng Redux. Bạn có sử dụng redux trong dự án của mình không? Ý kiến của bạn thế nào? Để lại bình luận bên dưới nhé.
😅 Đọc thêm:
anh có kênh youtube hay face k anh. hay cho em xin email đi ạ :)))
Kênh Youtube của mình nhé https://www.youtube.com/c/VNTALKING
anh ơi nếu vẫn dùng cả redux với apollo-client thì anh nghĩ sao ạ? em thấy vẫn nên cần có redux vì nó vẫn có lợi và công việc với client cũng đơn giản hơn khi dùng sự kết hợp này ạ. Mong anh cho em lời khuyên!
Cả hai thư viện này đều là quản lý state, trong đó apollo-client thì có vẻ mới hơn, hiện đại hơn. Nhưng cá nhân mình thì không cần thiết phải kết hợp cả hai vào cùng một dự án, nó sẽ dẫm đạp nên nhau.
hay anh ạ