+7

Redux middleware

Middleware có thể sử dụng cho nhiều mục đích khác nhau bao gồm gọi API không đồng bộ. Nó cung cấp một bên thứ ba để can thiệp vào giữa thời điểm dispatch một action và thời điểm action được chuyển đến reducer Redux middleware sẽ chặn lại các action để can thiệp và chỉnh sửa action đó hoặc nó có thể huỷ bỏ action bất kỳ bằng cách không gọi middleware tiếp theo.

Screenshot_2016_10_24_23_10_34.png

I. Redux Dispatch Function

  • Một Store trong Redux có một hàm dispatch và nó chỉ có nhiệm vụ dispatch action từ reducer function để cập nhật state của application.
  • Redux reducer function sẽ lấy một state và action parameter sau đó trả về một state mới.
reducer:: state -> action - state

Bạn có thể dispatch bất cứ một action nào, hay đơn giản như action bên dưới

{type:ADD_BOOK, text:ReactJS}

store này sẽ dispatch action tới tất cả các hàm reducers mà có thể thay đổi state. Thế nhưng reducer functions chỉ quan tâm đến việc thực thi ADD_BOOK, về cơ bản thì nó không quan tấm đến việc ai đã làm, làm mất trong bao lâu … và middleware có thể giúp chúng ta trả lời những câu hỏi này.

II. Redux Middleware

  • Redux middleware được thiết kế bằng cách tạo các functions có thể được kết hợp lại với nhau trước khi phương thức dispatch chính được gọi.
  • Hãy xem hàm logger middleware dưới đây nó có thể in ra trạng thái của application trước và sau khi chạy main dispatch function.
    export default function createLogger({ getState }) {
        return (next) =>
        (action) => {
            const console = window.console;
            const prevState = getState();
            const returnValue = next(action);
            const nextState = getState();
            const actionType = String(action.type);
            const message = `action ${actionType}`;
            console.log(`%c prev state`, `color: #9E9E9E`, prevState);
            console.log(`%c action`, `color: #03A9F4`, action);
            console.log(`%c next state`, `color: #4CAF50`, nextState);
            return returnValue;
        };
    }
  • Hàm createLogger có parameter là phương thức getState được được inject bởi applyMiddleware.js và được sử dụng để lấy current state. createLogger trả về một function mới với parameter là next được sử dụng để kết hợp chuỗi middleware function tiếp theo hoặc để dispatch function. Và hàm trả về với next parameter này cũng trả về một function với parameter là action object có thể được đọc hoặc chỉnh sửa trước khi gửi chúng tới middleware function tiếp theo của chuỗi. Cuối cùng hàm main dispatch được gọi với action object.

  • Có thể tóm lược quá trình thực thi của createLogger funtion như sau:

    • Đầu tiên nó sẽ lấy state trước đó
    • Acton được dispatch to một hàm middleware tiếp theo
    • Tất cả các middleware function sẽ được gọi theo theo thứ tự trong chuỗi function đó
    • Reducer function trong store được gọi với action payload, tính toán trả về state mới
    • Sau đó logger middleware sẽ lấy kết quả của trạng thái tiếp theo

II.1. Mổ xẻ applyMiddleware.js

    export default function applyMiddleware(...middlewares) {
     return (next) =>
       (reducer, initialState) => {
         var store = next(reducer, initialState);
         var dispatch = store.dispatch;
         var chain = [];
         var middlewareAPI = {
           getState: store.getState,
           dispatch: (action) => dispatch(action)
         };
         chain = middlewares.map(middleware =>
                          middleware(middlewareAPI));
         dispatch = compose(...chain, store.dispatch);
         return {
           ...store,
           dispatch
         };
      };
    }
export default function applyMiddleware(...middlewares)

Không có gì đặc biệt ở đây ngoại trừ tham số ...middlewares với cú pháp của ES6 điều đó có nghĩa là bạn có thể truyền vào nhiều middleware cho hàm.

Tiếp đến là hàm với parameter next

return (next) => (reducer, initialState) => {...}

Function với đối số next này sẽ được dùng để tạo store. Tiếp theo chúng ta sẽ chuyển việc thực thi store cho một function chịu trách nhiệm việc tạo store mới. Sau đó ta sẽ tạo biến dispatch cho dispatch store function cuối cùng sẽ tạo ra một mảng để giữ chuỗi middleware function.

var store = next(reducer, initialState);
var dispatch = store.dispatch;
var chain = [];

Tiếp đến làm một object được inject bởi hàm getState và dispatch function từ store vào trong mỗi middleware function bạn có thể tuỳ ý sử dụng middleware , kết quả là middleware được lưu vào một mảng.

var middlewareAPI = {
  getState: store.getState,
  dispatch: (action) => dispatch(action)
};
chain = middlewares.map(middleware =>
                    middleware(middlewareAPI));

Tiếp đến chúng ta sẽ tạo replacement dispatch với các thông tin về chuỗi middleware.

dispatch = compose(...chain, store.dispatch);

Trong đó compose là hàm được cung cấp bởi Redux và là một hàm để kết hợp các middleware lại với nhau.

II.2. Asynchronous Middleware

Giả sử bạn có một action creator nó có hàm gọi không đồng bộ như sau:

function fetchQuote(symbol) {
   requestQuote(symbol);
   return fetch(`http://www.google.com/finance/info?q=${symbol}`)
      .then(req => req.json())
      .then(json => showCurrentQuote(symbol, json));

}

Không có cách nào để dispatch một action mà nó được trả về từ hàm fetch, do đó chúng ta có thể sử dụng redux-thunk middleware để thực thi, bằng cách đưa việc thực thi này vào một function để bạn có thể delay việc thực thi này.

function fetchQuote(symbol) {
  return dispatch => {
    dispatch(requestQuote(symbol));
    return fetch(`http://www.google.com/finance/info?q=${symbol}`)
      .then(req => req.json())
      .then(json => dispatch(showCurrentQuote(symbol, json)));
  }
}

applyMiddleware function sẽ inject parameter dispatch và getState vào trong redux-thunk middleware. Bây giờ bạn có thể dispatch action object tới store có chưa reducers.

export default function thunkMiddleware({ dispatch, getState }) {
  return next =>
     action =>
       typeof action ===function?
         action(dispatch, getState) :
         next(action);
}

Nếu một action là một function nó sẽ được gọi với dispatch và getState function ngược lại nếu nó chỉ là một action bình thường thì thì nó cần phải được dispatch tới store.

III. Demo

Demo dưới đây chỉ mô tả đơn giản cách thức middleware hoạt động thông qua ví dụ logger

Tạo một một function middleware logger như sau

const logger =(store) => (next) => (action) => {
  console.log("action", action);
  next(action);
}

Hàm trên là theo cú pháp của ES6 Arrow function. nhìn trông có vẻ dị nhưng nó tương đương với hàm dưới đây khi viết lại

const logger = function (store) {
  return function (next) {
    return function(action) {
      console.log("action: ", action);
      return next(action);
    }
  }
}

sau đó phải applyMiddleware

const middleware = applyMiddleware(logger);

Để add middleware rất đơn giản chỉ cần truyền nó vào parameter thứ 3 trong hàm createStore

const store = createStore(reducer, 1, middleware)

cuối cùng subscrible listener

store.subscribe(() => {
  console.log("Store changed:", store.getState());
})

hàm subscrible này được gọi bất cứ khi nào store được dispatch

xem kết quả trên dev-tools của browser

action Object {type: "INC"}
Store changed: 2
action Object {type: "DEC"}
Store changed: 1
action Object {type: "DEC"}
Store changed: 0

Ngoài ra bạn có thể can thiệp chỉnh sửa action trong middleware, giả sử ta sẽ đổi hết action type của action thành “INC”

const logger =(store) => (next) => (action) => {
  console.log("action", action);
  action.type = "INC";
  next(action);
}

xem kết quả

action Object {type: "INC"}
Store changed: 2
action Object {type: "DEC"}
Store changed: 3
action Object {type: "DEC"}
Store changed: 4

Mặc dù action type truyền vào của 2 action cuối là DEC nhưng khi đi qua middleware ta đã đổi nó thành INC dẫn đến state chỉ có tăng mà không giảm 2,3,4 khác với ban đầu là 2,1,0 github link: Tóm lại:

  • store gọi dispatch action
  • Middleware được gọi, thay đổi xử lý action trước khi action được đưa đến reducer
  • Tiếp đến reducer được gọi tính toàn đầu ra cho state mới
  • Khi state thay đổi view được cập nhật và kết thúc

Tổng kết

Bài viết trên mới chỉ đi qua các khái niệm của redux middleware, trong bài tới sẽ đi sâu hơn về gọi ajax, promise ... trongg middleware

nguồn tham khảo:

https://medium.com/@meagle/understanding-87566abcfb7a#.ef4bd73kl

http://redux.js.org/docs/advanced/Middleware.html


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.