Tại sao chúng ta nên dùng một middleware như redux thunk

  -  

Xin chào, hôm nay mình xin giới thiệu đến mọi người cách xử lý những action bất đồng bộ trong Redux bằng việc sử dụng Redux thunk. Bên cạnh đó cũng sẽ làm 1 ví dụ để các bạn có thể nắm rõ hơn về cách sử dụng redux-thunk

*
Bài viết gốc tại đây: https://www.juliandong.com/2020/03/xu-ly-asynchronous-redux-action-voi.html

1. Giới thiệu

Trong bài viết lần trước của mình tại đây và đây, mình đã giới thiệu về Redux và các áp dụng nó vào trong một project React. Trong đó, mọi action trong redux đều đồng bộ, tức là state sẽ được update ngay lập tức khi action được dispatch.Vậy sẽ ra sao nếu action của chúng ta là bất đồng bộ, có nghĩa là action cần gọi đến 1 API bên ngoài để lấy dữ liệu hoặc thực hiện 1 side-effect khiến cho kết quả không thể trả về ngay lập tức được?

Rất may mắn Redux có hỗ trợ các middleware để xử lý vấn đề với asynchronous action và side effect. Nổi tiếng nhất là redux-thunk và redux-saga. Trong phạm vi bài viết này mình sẽ giới thiệu với các bạn về redux-thunk.

Bạn đang xem: Tại sao chúng ta nên dùng một middleware như redux thunk

2. Redux-thunk và asynchronous actions

Asynchronous actions

Khi ta gọi 1 API bất đồng bộ, có 2 thời điểm ta cần quan tâm:

Thời điểm bắt đầu gọiThời điểm nhận được kết quả

Tại mỗi thời điểm trên ta đều cần thay đổi state của ứng dụng, và để làm điều đó, ta cần dispatch những action mà sẽ được reducer xử lý một cách đồng bộ. Thông thường với mỗi API call ta cần dispatch 3 loại action

Action thông báo cho reducer là bắt đầu thực hiện API call: Reducer sẽ xử lý action này bằng việc thay đổi cờ loading hoặc isFetching trong state. Khi đó UI sẽ hiển 1 spinner thể hiện dữ liệu đang được xử lýAction thông báo cho reducer là việc gọi API thành công: Reducer sẽ xử lý action này bằng việc cập nhật kết quả trả về từ API và đồng thời tắt cờ loading. UI khi đó sẽ ẩn spinner và hiển thị kết quả Action thông báo cho reducer là gọi thất bại : Reducer sẽ reset lại cờ loading , lưu lại error vào state và hiển thị error message ở UI

redux-thunk

thunk: là một cách gọi khác của function, nhưng nó có 1 điểm đặc biệt là nó là một hàm được trả về từ một hàm khác.

Như chúng ta đã biết về action trong redux chỉ đơn thuần là những plain object có chứa 1 field là type và bất kì dữ liệu nào ta muốn thêm vào

{ "type": "ACTION_TYPE", "payload" :"Anything you want"}Và action creator là một hàm trả về về một action (plain object)

const actionCreator = (data) => ({ type: "ACTION_TYPE", payload: data})Đối với redux-thunk, nó là 1 middleware cho phép action creator trả về một function (thunk) thay vì trả về plain object. Function này sẽ nhận tham số là hàm dispatch của store, và nó sẽ dispatch các action một cách đồng bộ bên trong thunk khi mà asynchronous call được gọi. Nói các khác, use-case thông thường nhất của redux-thunk là khi lấy dữ liệu từ external API, redux-thunk cho phép dispatch các action theo lifecycle của request đến API ngoài.

Xem thêm: Có Nên Mua Đầu Thu Kỹ Thuật Số Trung Quốc Không? Đầu Thu Kts Trung Quốc

Ví dụ: ta cần fetch dữ liệu của 1 API, đầu tiên ta sẽ dispatch 1 action để báo rằng dữ liệu đang được fetch, rồi tiếp đó nếu kết quả trả về thành công, ta sẽ dispatch 1 action để báo rằng việc fetch dữ liệu đã kết thúc và nhận được kết quả. Nếu việc fetch thất bại, ta sẽ dispatch 1 action để báo rằng việc fetch dữ liệu kết thúc và nhận về lỗi.

Xem thêm: Balancer Là Gì ? Cách Giao Dịch Trên Balancer Nền Tảng Defi Balancer Hấp Dẫn Không

Để nắm rõ hơn về redux-thunk được sử dụng như thế nào trong thực tế, chúng ta sẽ đi qua 1 demo ở phần tiếp theo.

3. Demo

Trong ví dụ này, mình sẽ tạo ra 1 react app có nhiệm vụ search user name từ github bằng việc sử dụng react, redux và redux-thunk

Khởi tạo project và cài đặt các package cần thiết

Mình sẽ dùng create react app để khởi tạo project

$ npm init react-app react-thunk # init project$ cd react-thunk $ npm i redux redux-thunk axios # install packagesSau khi khởi tạo xong, chúng ta tiến hành cấu trúc lại thư mục src như sau:

src/components: chứa các component dùng trong ứng dụngsrc/actions: chứa action của reduxsrc/reducer: chứa store và reducerssrc/service: các service để gọi APIsrc/style: chứa file css

Setup redux

Trong src/index.js

import React from "react";import ReactDOM from "react-dom";import "./index.css";import App from "./components/App";import { Provider } from "react-redux";import {store} from "./reducer/store";import * as serviceWorker from "./serviceWorker";ReactDOM.render( , document.getElementById("root"));serviceWorker.unregister();Thêm redux thunk vào src/reducer/store.js

import { createStore, applyMiddleware } from "redux";import thunk from "redux-thunk";import {rootReducer} from "./reducers";export const store = createStore(rootReducer, applyMiddleware(thunk));

Viết service gọi API

Trong src/services/index.js

import axios from "axios";const fetchUserService = username => { return new Promise((resolve, reject) => { axios.get(`https://api.github.com/users/${username}`) .then(response => resolve(response.data)) .catch(error => reject(error)) })}export default fetchUserService;

Tạo action

Trong src/actions/fetchUser.js

import { FETCH_USER, FETCH_USER_FAILED, FETCH_USER_SUCCESS} from "./constants";import fetchUserSerivce from "../services";export default username => { return dispatch => { dispatch(fetchUser()); fetchUserSerivce(username) .then(user => dispatch(fetchUserSuccess(user))) .catch(error => dispatch(fetchUserFailed(error))) }}const fetchUser = () => ({ type: FETCH_USER});const fetchUserSuccess = user => ({ type: FETCH_USER_SUCCESS, payload: { user },})const fetchUserFailed = error => ({ type: FETCH_USER_FAILED, payload: { error }})

Tạo reducer

Trong src/reducer/reducers.js

import { FETCH_USER, FETCH_USER_SUCCESS, FETCH_USER_FAILED} from "../actions/constants";const initialState = { loading: false, error: null, user: null}export const rootReducer = (state = initialState, action) => { switch (action.type) { case FETCH_USER: return { loading: true, user: null, error: null, }; case FETCH_USER_SUCCESS: { return { loading: false, user: action.payload.user, error: null, }; } case FETCH_USER_FAILED: { return { loading: false, user: null, error: action.payload.error } } default: return state; }}Như vậy chúng ta đã setup redux xong, giờ sẽ hiện thực UI components

Search bar

Trong src/components/SearchBar.js

import React from "react";import fetchUser from "../actions/fetchUser";import { connect } from "react-redux";import "../style/SearchBar.css"class SearchBar extends React.Component { constructor(props) { super(props); this.state = { username: "" } this._onChange = this._onChange.bind(this); this._onSubmit = this._onSubmit.bind(this); } _onChange(event) { const value = event.target.value; this.setState({ username: value }) } _onSubmit(event) { event.preventDefault(); this.props.fetchUser(this.state.username) } render() { return (