iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 20
0
Modern Web

寫React的那些事系列 第 20

React Day20 - Redux Reducer

Reducer


Reducers接收action和prev state,並回傳新的state,action只傳遞要改變的類型(type)。而一開始的state,我們使用ES6 Default parameters定義在reducer function的parameter預設值中。

而寫Reducer應該要特別注意“不可以”的有三件事:

  • 不可以改變它的參數,尤其是prev state / next state。
  • 不可以執行有 side effect 的動作,像是呼叫setIntervalfetch api/AJAX,或是routing轉換等。
  • 不可以執行不能預測的code,像是Math.random()或是Date.now()

有注意到這三件事情都和我們先前提到 reducer is pure 有相關嗎!撰寫reducer的原則就是,傳給它的prev state、action和它回傳的next state,要是可以predictable的,所以這當中不能有隨機或是不可預期的code。

以下用filter的reducer當範例:

// filter預設初始值為 'SHOW_ALL'
function filter(state = 'SHOW_ALL', action) {
  switch (action.type) {
    // 根據傳入type,來處理next state並回傳
    case 'SET_FILTER':
      return action.filter;
    // 預設回傳原本的state,任何未定義的action也傳回prev state
    default:
      return state;
  }
}

上面示範的是改變string類型的state,如果是object和array的話,這邊必須特別小心,還記得reducer不可以有的第一件事情,就是「不可以改變它的參數」(prev state),所以object我們可以使用Object.assign重新指定,array也可以使用Spread Operator。

以下用todos,新增task來示範修改array的方式:

// todos預設初始值為 空陣列
function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TASK':
      return [
        ...state,
        {
          task: action.task,
          isCompleted: false
        }
      ];
    default:
      return state;
  }
}

再加上toggle task是否完成,示範修改object的方式:

function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TASK':
      return [
        ...state,
        {
          task: action.task,
          isCompleted: false
        }
      ]);
    case 'EDIT_TASK':
      // 使用Spread Operator把要改變的array列舉出來
      // 再用,Object.assign把要toggle的物件複製出來,並改變isCompleted
      return [
        ...state.slice(0, action.idx),
        Object.assign({}, state[action.idx], {
          isCompleted: !state[action.idx].isCompleted
        }),
        ...state.slice(action.idx + 1)
      ];
    default:
      return state;
  }
}

combineReducers()


每個reducer都是獨立處理的,也都會對應到它處理的state,所以通常會把不同reducer拆成不同檔案,避免檔案太長太複雜。在Redux中,我們使用combineReducers(),產生一個function,可以透過這個function來處理每一個reducer,而我們必須傳給combineReducers()每個state對應的reducer function,例如:combineReducers({ todos: todosReducer, filter: filterReducer }),這表示我們的state tree會是{ todos, filter }

使用方式,假設我們把filter和todos拆成兩個filter.js、todos.js,再用index.js把兩個reducers整合在一起,資料夾架構如下:

└── reducers
   ├── index.js
   ├── filter.js
   └── todos.js

首先要先安裝redux package:

npm install redux --save

todos.js和filter.js,要把function export出來,讓index.js可以import:

export default function todos(state = [], action) {
  // ...
}

export default function filter(state = 'SHOW_ALL', action) {
  // ...
}

reducers/index.js,使用combineReducers()把所有reducers組合:

import { combineReducers } from 'redux';

import filter from './filter';
import todos from './todos';

const todoApp = combineReducers({
  filter,
  todos
});

export default todoApp;

這樣就完成了reducers的功能!目前已經建立reducers在原本的todos範例中,更新的檔案放在Git 上


上一篇
React Day19 - Redux Action
下一篇
React Day21 - Redux Store
系列文
寫React的那些事31

1 則留言

0
imakou
iT邦新手 5 級 ‧ 2016-12-21 05:50:37

你好,
謝謝文章講解,想確認我的想法是不是對的

所以每個Reducer 都會依據Action去做出它們應該吐出來(return)的資料
並且將這些資料拿去給state tree。

設定state的方式就好像
todos:todosReducer

然後這個todos state就可以被存進去唯一的Store,
接著這些state就可以被當作全域state 然後被componets 使用?

不知道我這樣想是不是對的呢?

我覺得您講的很仔細,很棒。

chiouchu iT邦新手 5 級 ‧ 2016-12-21 11:48:51 檢舉

你描述的過程沒錯喔!設定state的方式,就是透過combineReducers,然後今天會繼續講store的部分喔!

非常感謝你的鼓勵~~
/images/emoticon/emoticon07.gif

我要留言

立即登入留言