Action 是把數據從應用傳到 store 的有效載荷。它是 store 數據的惟一來源。通常來講你會經過 store.dispatch()
將 action 傳到 store。javascript
Action 建立函數 就是生成 action 的方法。「action」 和 「action 建立函數」 這兩個概念很容易混在一塊兒,使用時最好注意區分。html
在 Redux 中的 action 建立函數只是簡單的返回一個 action:java
function addTodo(text) { return { type: ADD_TODO, text } }
/* * action 類型 */ export const ADD_TODO = 'ADD_TODO'; export const TOGGLE_TODO = 'TOGGLE_TODO' export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER' /* * 其它的常量 */ export const VisibilityFilters = { SHOW_ALL: 'SHOW_ALL', SHOW_COMPLETED: 'SHOW_COMPLETED', SHOW_ACTIVE: 'SHOW_ACTIVE' } /* * action 建立函數 */ export function addTodo(text) { return { type: ADD_TODO, text } } export function toggleTodo(index) { return { type: TOGGLE_TODO, index } } export function setVisibilityFilter(filter) { return { type: SET_VISIBILITY_FILTER, filter } }
Action 只是描述了有事情發生了這一事實,並無指明應用如何更新 state。而這正是 reducer 要作的事情。git
如今咱們已經肯定了 state 對象的結構,就能夠開始開發 reducer。reducer 就是一個純函數,接收舊的 state 和 action,返回新的 state。github
(previousState, action) => newState
之因此將這樣的函數稱之爲reducer,是由於這種函數與被傳入 Array.prototype.reduce(reducer, ?initialValue)
裏的回調函數屬於相同的類型。保持 reducer 純淨很是重要。永遠不要在 reducer 裏作這些操做:redux
Date.now()
或 Math.random()
。reduce:api
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce數組
reducer 必定要保持純淨。只要傳入參數相同,返回計算獲得的下一個 state 就必定相同。沒有特殊狀況、沒有反作用,沒有 API 請求、沒有變量修改,單純執行計算。dom
明白了這些以後,就能夠開始編寫 reducer,並讓它來處理以前定義過的 action。函數
咱們將以指定 state 的初始狀態做爲開始。Redux 首次執行時,state 爲 undefined
,此時咱們可藉機設置並返回應用的初始 state。
import { VisibilityFilters } from './actions' const initialState = { visibilityFilter: VisibilityFilters.SHOW_ALL, todos: [] }; function todoApp(state, action) { if (typeof state === 'undefined') { return initialState } // 這裏暫不處理任何 action, // 僅返回傳入的 state。 return state }
這裏一個技巧是使用 ES6 參數默認值語法 來精簡代碼。
function todoApp(state = initialState, action) { // 這裏暫不處理任何 action, // 僅返回傳入的 state。 return state }
如今能夠處理 SET_VISIBILITY_FILTER
。須要作的只是改變 state 中的 visibilityFilter
。
function todoApp(state = initialState, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return Object.assign({}, state, { visibilityFilter: action.filter }) default: return state } }
注意:
不要修改 state
。 使用 Object.assign()
新建了一個副本。不能這樣使用 Object.assign(state,{ visibilityFilter: action.filter })
,由於它會改變第一個參數的值。你必須把第一個參數設置爲空對象。你也能夠開啓對ES7提案對象展開運算符的支持, 從而使用 { ...state, ...newState }
達到相同的目的。
在 default
狀況下返回舊的 state
。遇到未知的 action 時,必定要返回舊的 state
。
還有兩個 action 須要處理。讓咱們先處理 ADD_TODO
。
function todoApp(state = initialState, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return Object.assign({}, state, { visibilityFilter: action.filter }) case ADD_TODO: return Object.assign({}, state, { todos: [ ...state.todos, { text: action.text, completed: false } ] }) default: return state } }
如上,不直接修改 state
中的字段,而是返回新對象。新的 todos
對象就至關於舊的 todos
在末尾加上新建的 todo。而這個新的 todo 又是基於 action 中的數據建立的。
最後,TOGGLE_TODO
的實現也很好理解:
case TOGGLE_TODO: return Object.assign({}, state, { todos: state.todos.map((todo, index) => { if (index === action.index) { return Object.assign({}, todo, { completed: !todo.completed }) } return todo }) })
目前的代碼看起來有些冗長:
function todoApp(state = initialState, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return Object.assign({}, state, { visibilityFilter: action.filter }) case ADD_TODO: return Object.assign({}, state, { todos: [ ...state.todos, { text: action.text, completed: false } ] }) case TOGGLE_TODO: return Object.assign({}, state, { todos: state.todos.map((todo, index) => { if(index === action.index) { return Object.assign({}, todo, { completed: !todo.completed }) } return todo }) }) default: return state } }
上面代碼可否變得更通俗易懂?這裏的 todos
和 visibilityFilter
的更新看起來是相互獨立的。有時 state 中的字段是相互依賴的,須要認真考慮,但在這個案例中咱們能夠把 todos
更新的業務邏輯拆分到一個單獨的函數裏:
function todos(state = [], action) { switch (action.type) { case ADD_TODO: return [ ...state, { text: action.text, completed: false } ] case TOGGLE_TODO: return state.map((todo, index) => { if (index === action.index) { return Object.assign({}, todo, { completed: !todo.completed }) } return todo }) default: return state } } function todoApp(state = initialState, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return Object.assign({}, state, { visibilityFilter: action.filter }) case ADD_TODO: case TOGGLE_TODO: return Object.assign({}, state, { todos: todos(state.todos, action) }) default: return state } }
注意 todos
依舊接收 state
,但它變成了一個數組!如今 todoApp
只把須要更新的一部分 state 傳給 todos
函數,todos
函數本身肯定如何更新這部分數據。這就是所謂的 reducer 合成,它是開發 Redux 應用最基礎的模式。
下面深刻探討一下如何作 reducer 合成。可否抽出一個 reducer 來專門管理 visibilityFilter
?固然能夠:
function visibilityFilter(state = SHOW_ALL, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return action.filter default: return state } }
combineReducers()
所作的只是生成一個函數,這個函數來調用你的一系列 reducer,每一個 reducer 根據它們的 key 來篩選出 state 中的一部分數據並處理,而後這個生成的函數再將全部 reducer 的結果合併成一個大的對象。沒有任何魔法。正如其餘 reducers,若是 combineReducers() 中包含的全部 reducers 都沒有更改 state,那麼也就不會建立一個新的對象。
最後,Redux 提供了 combineReducers()
工具類來作上面 todoApp
作的事情,這樣就能消滅一些樣板代碼了。有了它,能夠這樣重構 todoApp
:
import { combineReducers } from 'redux'; const todoApp = combineReducers({ visibilityFilter, todos }) export default todoApp;
export default function todoApp(state = {}, action) { return { visibilityFilter: visibilityFilter(state.visibilityFilter, action), todos: todos(state.todos, action) } }
const reducer = combineReducers({ a: doSomethingWithA, b: processB, c: c })
function reducer(state = {}, action) { return { a: doSomethingWithA(state.a, action), b: processB(state.b, action), c: c(state.c, action) } }
import { combineReducers } from 'redux' import { ADD_TODO, TOGGLE_TODO, SET_VISIBILITY_FILTER, VisibilityFilters } from './actions' const { SHOW_ALL } = VisibilityFilters function visibilityFilter(state = SHOW_ALL, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return action.filter default: return state } } function todos(state = [], action) { switch (action.type) { case ADD_TODO: return [ ...state, { text: action.text, completed: false } ] case TOGGLE_TODO: return state.map((todo, index) => { if (index === action.index) { return Object.assign({}, todo, { completed: !todo.completed }) } return todo }) default: return state } } const todoApp = combineReducers({ visibilityFilter, todos }) export default todoApp