redux 入門介紹

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

  • redux 介紹

    • redux 是什麼

    • redux 概念

    • redux 三原則

  • redux Stores

  • redux Action

  • redux Reducers

  • redux 數據流動

3.1.1 redux 介紹

redux 是什麼

Redux is a predictable state container for JavaScript apps

譯:Redux 是為 Javascript 應用而生的可預估的狀態容器

定義有些抽象,簡單來講 redux 可以理解為基於 flux 和其他一些思想(Elm,函數式編程)發展出來的前端應用架構庫,作為一個前端數據狀態容器體現,並可以在 React 和其他任何前端框架中使用。其中可預估性指的是 redux 中 reducer 的純函數特性,相同的指令總會得到一致的結果。

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

首先 render 是一個純函數,接收到之前的應用狀態和 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.jsvar 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)

推薦閱讀:

靜態網站生成器是如何工作的
這種線條是怎麼畫出來的?
WebSocket
如何精確控制textarea的行數,若輸入超過指定的行數,則禁止輸入?
請問這個網頁效果怎樣做?

TAG:React | 前端开发 | JavaScript |