精益 React 學習指南 (Lean React)- 3.1 redux 介紹

書籍完整目錄前端

3.1 開始使用 redux

clipboard.png

前面咱們介紹了 flux 架構以及其開源實現 redux,在這一節中,咱們將完整的介紹 redux:react

  • redux 介紹git

    • redux 是什麼github

    • redux 概念shell

    • redux 三原則npm

  • redux Stores編程

  • redux Actionredux

  • redux Reducerssegmentfault

  • redux 數據流動前端框架

3.1.1 redux 介紹

redux 是什麼

Redux is a predictable state container for JavaScript apps
譯:Redux 是爲 Javascript 應用而生的可預估的狀態容器

定義有些抽象,簡單來說 redux 能夠理解爲基於 flux 和其餘一些思想(Elm,函數式編程)發展出來的前端應用架構庫,做爲一個前端數據狀態容器體現,並能夠在 React 和其餘任何前端框架中使用。

redux 概念

  • store: 同 flux,應用數據的存儲中心

  • action: 同 flux ,應用數據的改變的描述

  • reducer: 決定應用數據新狀態的函數,接收應用以前的狀態和一個 action 返回數據的新狀態。

  • middleware: redux 提供中間件的方式,完成一些 flux 流程的自定義控制,同時造成其插件體系

redux 三原則

單一的 store

區別於 flux 模式中能夠有多個 state,整個應用的數據以樹狀結構存放在一個 state 對象中。

console.log(store.getState())
/* Prints
{
  visibilityFilter: 'SHOW_ALL',
  todos: [
    {
      text: 'Consider using Redux',
      completed: true,
    },
    {
      text: 'Keep all state in a single tree',
      completed: false
    }
  ]
}
*/

state 只讀

state 包含的應用數據不能隨意修改,修改數據的惟一方式是 dispatch action,action 描述要修改的信息(這和 flux 架構上是一致的,不過在設計上更加嚴格,見後面的 reducer 設計)。

store.dispatch({
  type: 'COMPLETE_TODO',
  index: 1
})

store.dispatch({
  type: 'SET_VISIBILITY_FILTER',
  filter: 'SHOW_COMPLETED'
})

數據的改變由純函數生成

在 redux 中,應用數據的改變路徑爲:

  1. store.dispatch(action)

  2. newState = reducer(previousState, action)

  3. reducer 爲純函數

純函數是函數是編程的思想,只要參數相同,函數返回的結果永遠是一致的,而且不會對外部有任何影響(不會改變參數對象的數據)。也就是說 reducer 每次必須返回一個新狀態,新狀態由舊狀態和 action 數據決定。

3.1.2 安裝 redux

安裝 redux 核心和 react-redux 集成進 react

$ npm install --save redux
$ npm install --save react-redux

3.1.3 redux store

在 redux 中 store 做爲一個單例,存放了應用的全部數據,對外提供了以下的 API:

  1. 獲取數據 store.getState()

  2. 經過觸發 action 來更新數據 store.dispatch(action)

  3. pubsub 模式提供消息訂閱和取消 store.subscribe(listener)

建立並初始化 store

redux 提供 createStore 方法來建立一個 store

/**
 * [createStore description]
 * @param  {[type]} reducer      [reducer 函數]
 * @param  {[type]} initialState [應用初始化狀態]
 * @return {[type]}              [description]
 */
function createStore(reducer, initialState) {
    // ....
}

建立一個 store :

var redux = require('redux');
var todoAppReducer = require('./reducers');
var initalState = {
    todos: []
};
var store = redux.createStore(todoAppReducer, initialState);

觸發 action

redux 修改應用數據的惟一方式爲 store.dispatch(action)

eg:

store.dispatch({
  type: 'ADD_TODO',
  title: 'new todo'
})

消息訂閱和取消

爲了讓用戶監聽應用數據改變,redux 在 store 集成了 pubsub 模式

訂閱

// 每次應用數據改變,輸出最新的應用數據
store.subscribe(function(){
  console.log(store.getState())
})

// 若是新增了 todo 會觸發上面的訂閱函數
store.dispatch({
  type: 'ADD_TODO',
  title: 'new todo'
})

取消

subscribe 返回的函數即爲取消函數

var unsubscribe = store.subscribe(function(){
  console.log(store.getState())
})

// ....

unsubscribe();

設計應用數據結構

全部數據都存放在一個 store 中,以 todoApp 爲例子,state 的數據結構可能爲

{
  visibilityFilter: 'SHOW_ALL',
  todos: [
    {
      text: 'Consider using Redux',
      completed: true,
    },
    {
      text: 'Keep all state in a single tree',
      completed: false
    }
  ]
}

當應用變大的時候,數據結構可能沒有這麼簡單,這時候須要找到一個較好的結構來設計應用數據,下面是兩個 redux 在設計 state 上的 tip:

  1. 業務數據和 UI 狀態數據分離,儘可能避免 UI 狀態數據放在 store 中,即使放在 store 中也好和業務數據分離。

  2. 避免嵌套,在一個複雜的場景,數據對象可能很深,出現多層,那在設計的時候能夠經過 id 的方式來引用,能夠參考 normalizr 的簡化方式

3.1.4 redux action

咱們已經知道 action 就是數據修改的描述信息,不過在實際使用過程當中須要理解下面的這些規範:

  1. action 描述數據結構

  2. action 類型常量

  3. action creator

action 描述數據結構

redux 對 action 對象的數據結構作了簡單規範,每一個對象必須包含一個字段 type,應用經過 type 來識別 action 類型,其餘字段不作任何限制。

eg:

{
  type: "ADD_TODO",
  text: 'Build my first Redux app'
}

{
  type: "REMVOE_TODO",
  index: 1
}

{
  type: "TOGGLE_TODO",
  id: 'a1s2d1'
}

action 類型常量

爲了項目的規範,一般把 action type 定義爲名稱常量,放在一個單獨的文件中管理,這在大型項目中是一個很好的習慣。

eg:

var ADD_TODO = "ADD_TODO"

{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}

action creator

在 flux 模式小節已經介紹過,爲了規範化 action 經過 action creator 來建立

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

原來的 dispatch 的使用改成了

// 舊的方式
store.dispatch({
    type: ADD_TODO,
    text: text
})

// 新的方式
store.dispatch(addTodo(text))

3.1.5 redux reducer

reducer 應該是最爲陌生的概念,理解 reducer 是理解 redux 的關鍵,牢記 reducer 決定應用數據的改變

reducer 基礎

/**
 * [reducer description]
 * @param  {[type]} previewsState [以前狀態]
 * @param  {[type]} action        [redux action]
 * @return {[type]} newState      [新狀態]
 */
function reducer(previewsState, action) {
    var newState;
    switch(action.type) {
        case ..
        case ..
        default:
            newState = previewsState
    }
    return newState;
}

首先 reducer 是一個純函數,接收到以前的應用狀態和 action 並返回一個新狀態,爲了每次返回一個新狀態,能夠經過 Object.assign() 方法返回一個新的對象,也可使用 Immutable.js (後面的章節會講解 Immutable.js)。

function todoApp(state, action) {
    state = initialState
    switch (action.type) {
        case SET_VISIBILITY_FILTER:
          return Object.assign({}, state, {
            visibilityFilter: action.filter
          })
        default:
          return state
    }
}

redux 會在初始的時候調用一次 reducer (這時參數 previewsState 爲 undefined), 能夠借用這個機會返回應用初始狀態數據。

eg:

// reducers/todoApp.js

var initialState = {todos: ...};

function todoApp(state, action) {
  if (typeof state === 'undefined') {
    return initialState
  }
  // 返回默認值
  return state
}

module.exports = todoApp;

總結須要注意的點:

  1. 純函數特性,不能修改 previewsState,不能調用異步 API,不管何時,傳入相同的參數都能當即返回相同的結果(不能調用 Math.random, Data.now 這些函數,由於會致使不一樣的結果)

  2. 默認返回 previewsState (在 action 不會獲得處理的狀況)

  3. 處理 state 爲 undefined 的狀況

reducer 組合

一個 todo 應用可能有不少操做,在真實場景上面的 todoApp 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
  }
}

這時候能夠對 todoApp reducer 作拆分,將它拆分爲多個不一樣的 reducer,todoApp reducer 的做用只是組合其餘 reducer 。

拆分後能夠以下:

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 visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter
    default:
      return state
  }
}

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

module.exports = todoApp

todoApp 再也不負責整個應用狀態的修改,而是將責任分配給了其餘的 reducer, 每一個子 reducer 相互獨立,負責各自的責任,這個時候應用數據的改變不是靠一個 reducer 改變的,而是由多個 reducer 組合起來,這是 redux 應用的基礎,叫作 reducer 組合

能夠發現 reducer 的組合和狀態數據的結構是相同的,均可以理解爲樹狀結構。 state 根對象有一個根 reducer (createState 傳入的 reducer)這裏是 todoApp , state 對象下面的第一級數據對應不用的 reducer,visibilityFilter 和 todos , 理解起來這也是一個典型的 分而治之策略

對於 reducer 的組合,redux 提供了 combineReducers() 方法來簡化,上面的 todoApp 能夠簡化爲:

var todoApp = redux.combineReducers({
  visibilityFilter: visibilityFilter,
  todos: todos
})

這樣的寫法和上面的寫法做用徹底相同

3.1.5 redux 數據流動

redux 繼承 flux 最核心的地方是 數據的單向流動
上面已經詳細介紹了 redux 的各個概念,下面將這些概念結合起來,看看數據怎麼在 redux 中流動的。

第一步:調用 store.dispatch(action)

能夠在任何地方觸發 dispatch,例如 UI 交互,API 調用

第二步: Redux store 調用 rootReducer

redux 收到 action 事後,調用根 reducer 並返回最新的狀態數據。(根 reducer 內部組合其餘 reducer 返回部分的最新狀態)

第三步:接收新狀態並 publish 給訂閱者

當 rootReducer 返回最新的狀態後,通知訂閱函數 store.subscribe(listener) 。在 React 中,能夠訂閱狀態更新,在訂閱函數中獲取最新的狀態事後,修改根組件的數據:

component.setState(newState)
相關文章
相關標籤/搜索