Để hiểu rõ hơn khái niệm Redux middleware, chúng ta cùng thử xử lý 1 bài toán như sau:

Bạn cần phải ngăn chặn người dùng (users) không được sử dụng các từ khóa bị cấm (ví dụ như vl, vkl, cmnr, …) trong bài viết của họ. Để đơn giản thì mình sẽ xử lý lúc người dùng submit bài viết (trong ví dụ sẽ chỉ check trong tiêu đề bài viết thôi nhé)

  handleSubmit(event) {
    event.preventDefault();
    const { title } = this.state;
    this.props.addArticle({ title });
    this.setState({ title: "" });
  }

Việc kiểm tra tất nhiên sẽ được đặt trước dòng this.props.addArticle và nó sẽ kiểu như này:

  handleSubmit(event) {
    event.preventDefault();
    const { title } = this.state;
    const forbiddenWords = ['spam', 'money'];
    const foundWord = forbiddenWords.filter(word => title.includes(word) )
    if (foundWord) {
      return this.props.titleForbidden();
    }
    this.props.addArticle({ title });
    this.setState({ title: "" });
  }

Việc làm như này tất nhiên cũng chẳng sao, tuy nhiên trong đoạn code trên chứa phần xử lý logic (business logic) và nó nằm trong UI Component; thế là không đạt được mục đích của Redux là chuyển logic ra khỏi React Component rồi. Redux middleware sinh ra để giải quyết bài toán trên cho mọi người. Hiểu 1 cách đơn giản thì Redux middleware cho phép chúng ta can thiệp vào giữa thời điểm dispatch 1 action và thời điểm action đến được reducer. Bạn có thể nhìn flow dưới đây để dễ hình dung.

Redux middleware là 1 function trả về 1 function, nhận next như 1 tham số. Bên trong function sẽ trả về 1 function khác nhận action như 1 tham số và cuối cùng là trả về next(action). Nó sẽ giống như thế này:

function forbiddenWordsMiddleware() {
  return function(next){
    return function(action){
      // do your stuff
      return next(action);
    }
  }
}

Ở đây bạn sẽ luôn luôn phải gọi next(action) ở trong middleware của bạn, nếu không thì Redux sẽ dừng lại, không có 1 action nào được đưa tới reducer. Trong middleware bạn cũng có thể truy cập vào state và sử dụng dispatch như sau:

function forbiddenWordsMiddleware({ getState, dispatch }) {
  return function(next){
    return function(action){
      // do your stuff
      return next(action);
    }
  }
}

Middleware trong Redux đóng vai trò rất quan trọng vì chúng sẽ chứa phần lớn logic ứng dụng của bạn với những lợi ích khi sử dụng:

  • Giúp tách biệt hầu hết logic ra khỏi giao diện (UI library)
  • Có khả năng tái sử dụng
  • Có thể test được 1 cách độc lập

Quay lại bài toán ban đầu, để sử dụng được middleware thì chúng ta phải apply nó vào trong store

import { createStore, applyMiddleware } from "redux";
import rootReducer from "../reducers/index";
import { forbiddenWordsMiddleware } from "../middleware";

const store = createStore(
  rootReducer,
  applyMiddleware(forbiddenWordsMiddleware)
);

export default store;

Khi submit action thêm mới bài viết (ADD_ARTICLE), middleware sẽ thực hiện việc check xem có từ khóa bị cấm hay không. Nếu không thì action thêm mới bài viết (ADD_ARTICLE) sẽ được thực hiện bình thường thông qua next(action). Nếu gặp từ khóa bị cấm, chúng ta sẽ dispatch 1 action khác mang tên là “FOUND_BAD_WORD” để gửi đến cho reducer. Code cụ thể sẽ được viết như dưới:

import { ADD_ARTICLE } from "../constants/action-types";

const forbiddenWords = ["spam", "money"];

export function forbiddenWordsMiddleware({ dispatch }) {
  return function(next) {
    return function(action) {
      // do your stuff
      if (action.type === ADD_ARTICLE) {
        
        const foundWord = forbiddenWords.filter(word =>
          action.payload.title.includes(word)
        );

        if (foundWord.length) {
          return dispatch({ type: "FOUND_BAD_WORD" });
        }
      }
      return next(action);
    };
  };
}

Ở ví dụ trong bài hôm nay, các xử lý của chúng ta đều là xử lý đồng bộ. Bài toán sẽ trở nên phức tạp hơn nếu như việc check từ khóa bị cấm cần thiết phải thông qua việc call API từ phía backend. Lúc đó cần xử lý bất đồng bộ. Tuy nhiên việc dispatch 1 action là đồng bộ; không thể thực hiện call 1 Promise trong này được. Nếu vẫn cố chày cối viết vào thì sẽ ra sao, thử xem nhé.

export function getData() {
  return fetch("https://jsonplaceholder.typicode.com/posts")
    .then(response => response.json())
    .then(json => {
      return { type: "DATA_LOADED", payload: json };
    });
}

Khi chạy app sẽ có lỗi: “Error: Actions must be plain objects. Use custom middleware for async actions”

Nó cho mình luôn câu trả lời nhé, đấy là sử dụng custom middleware. Và chúng ta sẽ giải quyết nó bằng Redux Thunk.

Mình sẽ giới thiệu Redux Thunk ở bài viết tiếp theo nhé, hôm nay tạm thời hiểu về middleware đã. Cảm ơn mọi người!