pcwu's TIL Notes


[Redux] Redux Thunk 小筆記

Intro

之前筆記有記錄過,Redux 其中一個很重要的概念,就是要保持 reducers 是完全的 pure 的,那所以進行 AJAX 這種充滿副作用的行為時,就需要在 actions 解決。

所以一個可能的場景就是想要 fetch 資料時,先發一個 action。在這個 action 內先做 fetch,等拿到資料後,再 dispatch 結果給 reducers 來完成,如此就可以將不確定性(例如連線失敗沒拿到資料)留在 actions 保持 reducers 的 pure 特性。

但正常 Redux 的 actionCreator 就是直接 dispatch 了,像這樣:

export const deleteTodo = todo => ({ type: 'deleteTodo', todo });

所以我們可以透過 redux-thunk 幫忙在 actionCreator 根據狀況 dispatch 不同的事件和內容。

Redux-Thunk

先讀一下 redux-thunk 官方介紹:

Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.

再來讀一下 redux-thunk 的原始碼:

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

沒錯,redux-thunk 這個4千多個星星的源始碼就只有短短14行。

可以看得出來,它主要只是一個 mddileware。當發現 action 不是正常的 Object 而是 function 時,就會將 dispatch, getState 等餵進執行,回傳得到正常的 Object 再交給 reducers

一個完整的例子:

const addTodoRequest = () => (
  { type: 'ADD_TODO_REQUEST' }
);

const addTodoSuccess = (todo) => (
  {
    type: 'ADD_TODO_SUCCESS',
    todo,
  };
);

const addTodoFailure = (err) => (
  {
    type: 'ADD_TODO_FAILURE',
    err,
  };  
);

const addTodo = (todo) => (dispatch) => {
  dispatch(addTaskRequest());
  return fetch('http://www.example.com/api', {
    method: 'POST',
    body: JSON.stringify({
      todo
    })
  })
  .then(res => {
    dispatch(addTodoSuccess(todo));
  })
  .catch(err => dispatch(addTodoFailure(err)));
};

所以當 addTodo 被執行時,到 redux-thunk 這個 middleware 時,會先被擋下來。

餵入 dispatch 後執行,這時會先 dispatch(addTaskRequest());reducers, 然後根據成功與否,接下來執行 dispatch(addTodoSuccess(todo));dispatch(addTodoFailure(err))

站在 reducers 的角度,就是當收到 addTaskRequest() 時,就可以改變 state 讓頁面顯示”載入中”之類的符號,直到得到 addTodoSuccess(todo)addTodoFailure(err) 時,才再改變 state 讓畫面顯示”新增成功”或”新增失敗”等訊息。

所以結論就是 redux-thunk 透過增加一個 middleware,可以豐富本來 actions 能做的事。

Reference