Redux -- 1 action Reducer

一.Action

1.概念

Action 是把數據從應用傳到 store 的有效載荷。它是 store 數據的惟一來源。通常來講你會經過 store.dispatch() 將 action 傳到 store。javascript

2.Action 建立函數

Action 建立函數 就是生成 action 的方法。「action」 和 「action 建立函數」 這兩個概念很容易混在一塊兒,使用時最好注意區分。html

在 Redux 中的 action 建立函數只是簡單的返回一個 action:java

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

3.actions.js

/*
 * 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 }
}

 

二. Reducer 

Action 只是描述了有事情發生了這一事實,並無指明應用如何更新 state。而這正是 reducer 要作的事情。git

1. Action 處理

如今咱們已經肯定了 state 對象的結構,就能夠開始開發 reducer。reducer 就是一個純函數,接收舊的 state 和 action,返回新的 state。github

(previousState, action) => newState

 之因此將這樣的函數稱之爲reducer,是由於這種函數與被傳入 Array.prototype.reduce(reducer, ?initialValue) 裏的回調函數屬於相同的類型。保持 reducer 純淨很是重要。永遠不要在 reducer 裏作這些操做:redux

  • 修改傳入參數;
  • 執行有反作用的操做,如 API 請求和路由跳轉;
  • 調用非純函數,如 Date.now() 或 Math.random()

reduce:api

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce數組

2. 編寫Reducer

 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
  }
}

注意:

  1. 不要修改 state 使用 Object.assign() 新建了一個副本。不能這樣使用 Object.assign(state,{ visibilityFilter: action.filter }),由於它會改變第一個參數的值。你必須把第一個參數設置爲空對象。你也能夠開啓對ES7提案對象展開運算符的支持, 從而使用 { ...state, ...newState }達到相同的目的。

  2. 在 default 狀況下返回舊的 state遇到未知的 action 時,必定要返回舊的 state

 

3.處理多個 action

還有兩個 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
    })
  })

4. 拆分 Reducer

目前的代碼看起來有些冗長:

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
  }
}

5. combineReducers

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;

1. 注意上面的寫法和下面徹底等價:

export default function todoApp(state = {}, action) {
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  }
}

 

2.你也能夠給它們設置不一樣的 key,或者調用不一樣的函數。下面兩種合成 reducer 方法徹底等價:

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)
  }
}

 

6. reducer.js

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
相關文章
相關標籤/搜索