=
=

Bài 4: Redux và React Redux

Giới thiệu Redux

Redux là gì?

Redux là một mẫu (pattern) và thư viện (library) để quản lý và cập nhật trạng thái (state) ứng dụng, sử dụng các sự kiện (events) được gọi là "hành động" (actions). Nó phục vụ như một kho lưu trữ (store) tập trung cho trạng thái cần được sử dụng trên toàn bộ ứng dụng của chúng ta, với các quy tắc đảm bảo rằng trạng thái chỉ có thể được cập nhật theo cách có thể dự đoán được.

Tại sao dùng Redux?

Redux giúp chúng ta quản lý trạng thái "toàn cục" (global) - trạng thái cần thiết trên nhiều phần ứng dụng của chúng ta. Các mẫu (patterns) và công cụ (tools) do Redux cung cấp giúp chúng ta dễ hiểu hơn khi nào, ở đâu, tại sao và cách trạng thái trong ứng dụng của chúng ta đang được cập nhật cũng như cách logic ứng dụng của chúng ta sẽ hoạt động khi những thay đổi đó xảy ra. Redux hướng dẫn chúng ta viết mã có thể dự đoán và kiểm tra được, điều này giúp chúng ta tự tin rằng ứng dụng của chúng ta sẽ hoạt động như mong đợi.

Khi nào dùng Redux?

Redux giúp chúng ta giải quyết vấn đề quản lý trạng thái được chia sẻ, nhưng giống như bất kỳ công cụ nào, nó cũng có sự cân bằng. Có nhiều khái niệm hơn để học và nhiều mã hơn để viết. Nó cũng thêm một số chỉ dẫn vào mã của chúng ta và yêu cầu chúng ta tuân theo các hạn chế nhất định. Đó là sự đánh đổi giữa năng suất ngắn hạn và dài hạn.

Redux hữu ích hơn khi:

Thư viện và công cụ Redux

Redux là một thư viện JS độc lập nhỏ. Tuy nhiên, nó thường được sử dụng với một số gói khác:

React-Redux (https://react-redux.js.org/)

Redux có thể tích hợp với bất kỳ khung giao diện người dùng nào và được sử dụng thường xuyên nhất với React. React-Redux là gói chính thức cho phép các thành phần React của chúng ta tương tác với kho lưu trữ Redux bằng cách đọc các phần trạng thái và gửi các hành động để cập nhật kho lưu trữ.

Bộ công cụ Redux (https://redux-toolkit.js.org/)

Bộ công cụ Redux là cách tiếp cận được khuyến nghị của chúng tôi để viết logic Redux. Nó chứa các gói và chức năng mà chúng tôi nghĩ là cần thiết để xây dựng một ứng dụng Redux. Bộ công cụ Redux xây dựng theo các phương pháp hay nhất được đề xuất của chúng tôi, đơn giản hóa hầu hết các tác vụ Redux, ngăn ngừa các lỗi phổ biến và giúp viết ứng dụng Redux dễ dàng hơn.

Phần mở rộng Redux DevTools (https://github.com/reduxjs/redux-devtools/tree/main/extension)

Phần mở rộng Redux DevTools hiển thị lịch sử những thay đổi đối với trạng thái trong kho lưu trữ Redux của chúng ta theo thời gian. Điều này cho phép chúng ta gỡ lỗi các ứng dụng của mình một cách hiệu quả, bao gồm cả việc sử dụng các kỹ thuật mạnh mẽ như "gỡ lỗi theo thời gian".

Thao tác cơ bản với Redux

Dưới đây là một số đối tượng và thao tác cơ bản trong Redux, cùng với ví dụ minh họa.

Action và Action Creator

Action là các đối tượng chứa thông tin về hành động sẽ được thực hiện. Một action cần phải có một thuộc tính type để xác định loại hành động và các thuộc tính khác để chứa dữ liệu.

Action creator là các hàm trong Redux được sử dụng để tạo ra các action - các đối tượng chứa dữ liệu và chỉ đạo cho reducer về cách thay đổi state của ứng dụng. Cụ thể, action creator là một hàm trả về một action. Một action creator thường trở thành điểm tập trung chính để tạo và định nghĩa các hành động (actions) mà ứng dụng của chúng ta có thể thực hiện. Nó giúp đơn giản hóa quá trình tạo action và giúp chúng ta duy trì các loại action cũng như giúp chúng ta tránh việc viết mã lặp đi lặp lại.

Ví dụ:

Trong đoạn mã trên, addTodo là một action creator. Khi chúng ta gọi hàm này, nó sẽ trả về một action với type là 'ADD_TODO' và một payload chứa thông tin về công việc mới cần thêm vào danh sách.

Khi chúng ta gọi một action creator và truyền các tham số (nếu cần), nó sẽ trả về một action mà chúng ta có thể dispatch đến Redux store. Các reducers sau đó có thể sử dụng thông tin trong action để cập nhật state của ứng dụng.

Reducer

Reducer là các hàm xử lý hành động và trả về trạng thái mới của ứng dụng. Reducer nhận vào hai tham số: trạng thái hiện tại và hành động. Ví dụ:

initialState là trạng thái ban đầu của ứng dụng. Nó là một object trong Redux chứa các thuộc tính đại diện cho trạng thái của ứng dụng. Trong trường hợp này, initialState chỉ có một thuộc tính là todos, đại diện cho danh sách các công việc.

rootReducer là một hàm reducer trong Redux. Nó nhận vào hai tham số: state (đại diện cho trạng thái hiện tại của ứng dụng) và action (đại diện cho hành động mà Redux store nhận được).

Trong trường hợp này, state được gán giá trị mặc định là initialState nếu nó không được truyền vào. Điều này đảm bảo rằng trạng thái ban đầu của ứng dụng là initialState.

Hàm switch được sử dụng để kiểm tra giá trị của action.type. Trong trường hợp này, chúng ta chỉ xử lý một hành động là ADD_TODO.

Khi hành động có type là ADD_TODO, rootReducer tạo một trạng thái mới bằng cách sao chép trạng thái hiện tại (...state) và thêm một công việc mới vào danh sách todos sử dụng dữ liệu được chứa trong action.payload.

Nếu hành động không có type là ADD_TODO hoặc nếu không có hành động nào được nhận biết, rootReducer chỉ đơn giản trả về trạng thái hiện tại mà không làm thay đổi gì.

Store

Store là nơi (kho) chứa toàn bộ trạng thái của ứng dụng. Nó làm nhiệm vụ phân phối hành động đến reducer và lưu trữ trạng thái mới. Ví dụ:

Chúng ta import hàm createStore từ thư viện Redux để sử dụng trong ứng dụng của mình.

Chúng ta sử dụng hàm createStore và truyền vào reducer (rootReducer) để tạo một Redux store. Reducer này sẽ xử lý các hành động và cập nhật trạng thái của ứng dụng dựa trên các hành động đó. Trong trường hợp này, rootReducer là reducer mà chúng ta đã định nghĩa để quản lý trạng thái của danh sách công việc (todos).

Redux store được lưu trữ trong biến store. Điều này cho phép chúng ta truy cập và tương tác với trạng thái của ứng dụng, gửi các hành động đến store để cập nhật trạng thái, và đăng ký các listeners để theo dõi sự thay đổi của trạng thái.

Dispatch

Dispatch là một phương thức của store, được sử dụng để gửi hành động đến reducer để cập nhật trạng thái. Ví dụ:

Subscribe

Subscribe là một phương thức của store để đăng ký một hàm callback, sẽ được gọi mỗi khi trạng thái của store thay đổi. Ví dụ:

Trên đây là các ví dụ đơn giản về cách sử dụng Redux trong một ứng dụng JavaScript hoặc React. Chúng ta có thể thấy cách action, reducer, store, dispatch, và subscribe hoạt động cùng nhau để quản lý trạng thái của ứng dụng. Các hành động được gửi đến reducer thông qua store, và trạng thái mới sau đó được lưu trữ trong store và thông báo đến các subscriber.

Một cách thuận tiện để sử dụng thư viện Redux trong React là sử dụng thư viện cầu nối React Redux sẽ được trình bày trong mục tiếp theo của bài này.

Kết hợp React và Redux dùng React Redux

React Redux là một thư viện kết hợp React với Redux để quản lý trạng thái của ứng dụng React một cách dễ dàng. Redux giúp quản lý trạng thái của ứng dụng, trong khi React giúp hiển thị giao diện người dùng dựa trên trạng thái đó. React Redux tạo ra một cách kết nối giữa Redux và React, giúp các component React có thể truy cập trạng thái Redux và gửi các hành động (actions) đến Redux store.

Ví dụ về Dự Án Hoàn Chỉnh Sử Dụng React Redux trong React

Dưới đây là một ví dụ về một ứng dụng Todo List sử dụng React Redux. Trong ứng dụng này, Redux được sử dụng để quản lý danh sách các công việc (todos). Chúng ta có thể thêm công việc mới, đánh dấu công việc đã hoàn thành, và xóa công việc.

Đầu tiên là cài đặt React Redux. Trong Visual Studio Code gõ lệnh sau trong Terminal:

npm install redux react-redux

Trong CodeSandbox tìm đến mục Dependencies và thêm các thư viện redux, react-redux.

Kế tiếp, thực hiện các thao tác cơ bản sau:

Ứng dụng được tạo trên CodeSandbox như sau:

Trong ví dụ này, TodoList component được kết nối với Redux sử dụng hàm connect() từ react-redux. mapStateToProps được sử dụng để chuyển đổi trạng thái từ Redux thành props, và mapDispatchToProps được sử dụng để chuyển đổi các hành động thành props. Bằng cách này, TodoList có thể gửi các hành động đến Redux store và nhận trạng thái từ store để hiển thị danh sách các công việc.