Redux開發實用教程

爲了幫助你們快速上手什麼是Redux開發,在這本節中將向你們介紹什麼是Redux開發所須要的一些什麼是Redux必備基礎以及高級知識html

什麼是Redux?

Redux 是 JavaScript 狀態容器,提供可預測化的狀態管理,可讓你構建一致化的應用,運行於不一樣的環境(客戶端、服務器、原生應用),而且易於測試。react

redux-flow

咱們過下整個工做流程:git

  1. 用戶(操做View)發出Action,發出方式就用到了dispatch方法;
  2. 而後,Store自動調用Reducer,而且傳入兩個參數(當前State和收到的Action),Reducer會返回新的State,若是有Middleware,Store會將當前State和收到的Action傳遞給Middleware,Middleware會調用Reducer 而後返回新的State;
  3. State一旦有變化,Store就會調用監聽函數,來更新View;

到這兒爲止,一次用戶交互流程結束。能夠看到,在整個流程中數據都是單向流動的。github

Redux和Flux的對比

Redux是Flux思想的一種實現,同時又在其基礎上作了改進。Redux秉承了Flux單向數據流、Store是惟一的數據源的思想。數據庫

  • Redux中沒有Dispatcher:它使用Store的Store.dispatch()方法來把action傳給Store,因爲全部的action處理都會通過這個Store.dispatch()方法,因此在Redux中很容易實現Middleware機制。Middleware可讓你在reducer執行前與執行後進行攔截並插入代碼,來達到操做action和Store的目的,這樣一來就很容易實現靈活的日誌打印、錯誤收集、API請求、路由等操做。
  • Redux只有一個Store:Flux中容許有多個Store,可是Redux中只容許有一個,相較於多個Store的Flux,一個Store更加清晰,並易於管理;

Redux和Flux的最大不一樣是Redux沒有 Dispatcher 且不支持多個 store。Redux只有一個單一的 store 和一個根級的 reduce 函數(reducer),隨着應用不斷變大,咱們須要將根級的 reducer 拆成多個小的 reducers,分別獨立地操做 state 樹的不一樣部分,而不是添加新的 stores。npm

Redux優勢

  • 可預測: 始終有一個惟一的準確的數據源(single source of truth)就是store,經過actions和reducers來保證整個應用狀態同步,作到毫不混亂
  • 易維護: 具有可預測的結果和嚴格的組織結構讓代碼更容易維護
  • 易測試: 編寫可測試代碼的首要準則是編寫能夠僅作一件事而且獨立的小函數(single responsibility principle),Redux的代碼幾乎所有都是這樣的函數:短小·純粹·分離

爲何要用Reudx?

隨着 JavaScript 應用愈來愈大,愈來愈複雜,咱們須要管理的state變得愈來愈多。 這些 state 可能包括服務器響應、緩存數據、本地生成還沒有持久化到服務器的數據,也包括 UI 狀態,如激活的路由,被選中的標籤,是否顯示加載動效或者分頁器等等。json

管理不斷變化的 state 很是困難。若是一個 model 的變化會引發另外一個 model 變化,那麼當 view 變化時,就可能引發對應 model 以及另外一個 model 的變化,依次地,可能會引發另外一個 view 的變化。直至你搞不清楚到底發生了什麼。state 在何時,因爲什麼緣由,如何變化已然不受控制。 當系統變得錯綜複雜的時候,想重現問題或者添加新功能就會變得很是複雜。redux

雖然React 試圖在視圖層禁止異步和直接操做 DOM 來解決這個問題。美中不足的是,React 依舊把處理 state 中數據的問題留給了你。Redux就是爲了幫你解決這個問題。react-native

Redux 的三個基本原則

  • 單一數據源:整個應用的 state 被儲存在一棵 object tree 中,而且這個 object tree 只存在於惟一一個 store 中;
  • State 是隻讀的:惟一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通對象;
  • 使用純函數來執行修改:爲了描述 action 如何改變 state tree ,你須要編寫 reducers;

Redux有那幾部分構成?

  • action:action就是一個描述發生什麼的對象;
  • reducer:形式爲 (state, action) => state 的純函數,功能是根據action 修改state 將其轉變成下一個 state;
  • store:用於存儲state,你能夠把它當作一個容器,整個應用只能有一個store。

Redux應用中全部的 state 都以一個對象樹的形式儲存在一個單一的 store 中。 唯一改變 state 的辦法是觸發 action,action就是一個描述發生什麼的對象。 爲了描述 action 如何改變 state 樹,你須要編寫 reducers。數組

先看一個redux的簡單使用例子:

import { createStore } from 'redux';

// 建立Redux reducer
/**
 * 這是一個 reducer,形式爲 (state, action) => state 的純函數。
 * 描述了 action 如何把 state 轉變成下一個 state。
 *
 * state 的形式取決於你,能夠是基本類型、數組、對象,
 * 當 state 變化時須要返回全新的對象,而不是修改傳入的參數。
 *
 * 下面例子使用 `switch` 語句和字符串來作判斷,但你能夠寫幫助類(helper)
 * 根據不一樣的約定(如方法映射)來判斷,只要適用你的項目便可。
 */
function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + 1;
  case 'DECREMENT': 
    return state - 1;
  default:
    return state;
  }
}

// 建立 Redux store 來存放應用的狀態。
// API 是 { subscribe, dispatch, getState }。
let store = createStore(counter);

// 能夠手動訂閱更新,也能夠事件綁定到視圖層。
store.subscribe(() =>
  console.log(store.getState())
);

// 改變內部 state 唯一方法是 dispatch 一個 action。
// action 能夠被序列化,用日記記錄和儲存下來,後期還能夠以回放的方式執行
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1
複製代碼

以上代碼即是一個redux的最簡單的使用,接下來咱們來分別介紹一下redux的三大組成部分:action、reducer以及store。

action

Action 是把數據從應用傳到 store 的有效載荷。它是 store 數據的惟一來源,也就是說要改變store中的state就須要觸發一個action。

Action 本質上一個普通的JavaScript對象。action 內必須使用一個字符串類型的 type 字段來表示將要執行的動做,除了 type 字段外,action 對象的結構徹底由你本身決定。多數狀況下,type 會被定義成字符串常量。當應用規模愈來愈大時,建議使用單獨的模塊或文件來存放 action。

import { ADD_TODO, REMOVE_TODO } from '../actionTypes'

//action
{
  type: ADD_TODO,
  text: 'Build my first Redux app'
} 
複製代碼

提示:使用單獨的模塊或文件來定義 action type 常量並非必須的,甚至根本不須要定義。對於小應用來講,使用字符串作 action type 更方便些。不過,在大型應用中把它們顯式地定義成常量仍是利大於弊的。

Action 建立函數

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

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

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}
複製代碼

這樣作將使 action 建立函數更容易被移植和測試。

reducer

reducer是根據action 修改state 將其轉變成下一個 state,記住 actions 只是描述了有事情發生了這一事實,並無描述應用如何更新 state。

(previousState, action) => newState
複製代碼

保持 reducer 純淨很是重要。永遠不要在 reducer 裏作這些操做:

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

提示:reducer 是純函數。它僅僅用於計算下一個 state。它應該是徹底可預測的:屢次傳入相同的輸入必須產生相同的輸出。它不該作有反作用的操做,如 API 調用或路由跳轉。這些應該在 dispatch action 前發生。

//reducer
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,visibilityFilter: action.filter } 達到相同的目的。
  • 在 default 狀況下返回舊的 state。遇到未知的 action 時,必定要返回舊的 state。

拆分與合併Reducer

function onAction(state = defaultState, action) {
    switch (action.type) {
        case Types.THEME_CHANGE://主題
            return {
                ...state,
                theme: action.theme,
            };
        case Types.SHOW_THEME_VIEW://主題
            return {
                ...state,
                customThemeViewVisible: action.customThemeViewVisible,
            };
        case Types.SORT_LANGUAGE://排序
            return Object.assign({}, state, {
                checkedArray: action.checkedArray,
            });
        case Types.REFRESH_ABOUT://關於
            return Object.assign({}, state, {
                [action.flag]: {
                    ...state[action.flag],
                    projectModels: action.projectModels,
                }
            });
        case Types.ABOUT_SHOW_MORE://關於
            return Object.assign({}, state, {
                me: {
                    ...state.me,
                    [action.menuFlag]: action.menuShow
                }
            });
        default:
            return state;
    }
}
複製代碼

上述代碼看起來有些冗長,而且主題、排序、關於的更新看起來是相互獨立的,能不能將他們拆到單獨的函數或文件裏呢,答案是能夠的。

拆分

//主題 theme.js
export default function onTheme(state = defaultState, action) {
    switch (action.type) {
        case Types.THEME_CHANGE:
            return {
                ...state,
                theme: action.theme,
            };
        case Types.SHOW_THEME_VIEW:
            return {
                ...state,
                customThemeViewVisible: action.customThemeViewVisible,
            };
        default:
            return state;
    }
}

//排序 sort.js
export default function onSort(state = defaultState, action) {
    switch (action.type) {
        case Types.SORT_LANGUAGE:
            return Object.assign({}, state, {
                checkedArray: action.checkedArray,
            });
        default:
            return state;
    }
}

//關於 about.js
export default function onAbout(state = defaultState, action) {
    switch (action.type) {
        case Types.REFRESH_ABOUT:
            return Object.assign({}, state, {
                [action.flag]: {
                    ...state[action.flag],
                    projectModels: action.projectModels,
                }
            });
        case Types.ABOUT_SHOW_MORE:
            return Object.assign({}, state, {
                me: {
                    ...state.me,
                    [action.menuFlag]: action.menuShow
                }
            });
        default:
            return state;
    }
}
複製代碼

在上述代碼中,咱們將對主題、排序、關於的操做拆到了單獨的函數中並放到了不一樣的文件裏,這樣以來各個模塊的操做就更加的聚合了,代碼看起來也就更加的簡潔明瞭。

合併reducer

通過上述的步驟咱們將一個大的reducer拆分紅了不一樣的小的reducer,但redux原則是隻容許一個根reducer,接下來咱們須要將這幾個小的reducer聚合到一個跟reducer中。

這裏咱們須要用到Redux 提供的combineReducers(reducers)

import {combineReducers} from 'redux'
import theme from './theme'
import sort from './sort'
import about from './about'

const index = combineReducers({
    theme: theme,
    sort: sort,
    about: about,
})
export default index;
複製代碼

combineReducers() 所作的只是生成一個函數,這個函數來調用你的一系列 reducer,每一個 reducer 根據它們的 key 來篩選出 state 中的一部分數據並處理,而後這個生成的函數再將全部 reducer 的結果合併成一個大的對象。沒有任何魔法。正如其餘 reducers,若是 combineReducers() 中包含的全部 reducers 都沒有更改 state,那麼也就不會建立一個新的對象。

Store

是存儲state的容器,Store 會把兩個參數(當前的 state 樹和 action)傳入 reducer。

store 有如下職責:

  • 維持應用的 state;
  • 提供 getState() 方法獲取 state;
  • 提供 dispatch(action) 方法更新 state:咱們能夠在任何地方調用 store.dispatch(action),包括組件中、XMLHttpRequest 回調中、甚至定時器中;
  • 經過 subscribe(listener) 註冊監聽器;
  • 經過 subscribe(listener) 返回的函數註銷監聽器。

在前一個章節中,咱們使用 combineReducers() 將多個 reducer 合併成爲一個。如今咱們經過Redux的 createStore()來建立一個Store。

import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp)
複製代碼

高級

異步Action

咱們上文中所講的Action都是基於同步實現的,那麼對於網絡請求數據庫加載等應用場景同步Action顯然是不適用的,對此咱們須要用到異步Action。

咱們可將異步Action簡答理解爲:在Action中進行異步操做等操做返回後再dispatch一個action。

爲了使用異步action咱們須要引入redux-thunk庫,redux-thunk是爲Redux提供異步action支持的中間件。

使用redux-thunk

npm install --save redux-thunk

import thunk from 'redux-thunk'
let middlewares = [
    thunk
]
//添加異步中間件redux-thunk
let createAppStore = applyMiddleware(...middlewares)(createStore)
複製代碼

建立異步action

export function onSearch(inputKey, token, popularKeys) {
    return dispatch => {
        dispatch({type: Types.SEARCH_REFRESH});
        fetch(genFetchUrl(inputKey)).then(response => {//若是任務取消,則不作任何處理
            return checkCancel(token) ? response.json() : null;
        }).then(responseData => {
            if (!checkCancel(token, true)) {//若是任務取消,則不作任何處理
                return
            }
            if (!responseData || !responseData.items || responseData.items.length === 0) {
                dispatch({type: Types.SEARCH_FAIL, message: inputKey + '什麼都沒找到'});
                return
            }
            let items = responseData.items;
            getFavoriteKeys(inputKey, dispatch, items, token, popularKeys);
        }).catch(e => {
            console.log(e);
            dispatch({type: Types.SEARCH_FAIL, error: e});
        })
    }
}
複製代碼

異步數據流

默認狀況下,createStore() 所建立的 Redux store 沒有使用 middleware,因此只支持 同步數據流。

你可使用 applyMiddleware() 來加強 createStore()。它能夠幫助你用簡便的方式來描述異步的 action。

像 redux-thunk 或 redux-promise 這樣支持異步的 middleware 都包裝了 store 的 dispatch() 方法,以此來讓你 dispatch 一些除了 action 之外的其餘內容,例如:函數或者 Promise。你所使用的任何 middleware 均可以以本身的方式解析你 dispatch 的任何內容,並繼續傳遞 actions 給下一個 middleware。好比,支持 Promise 的 middleware 可以攔截 Promise,而後爲每一個 Promise 異步地 dispatch 一對 begin/end actions。

當 middleware 鏈中的最後一個 middleware 開始 dispatch action 時,這個 action 必須是一個普通對象;

總結

  • Redux 應用只有一個單一的 store。當須要拆分數據處理邏輯時,你應該使用 reducer 組合 而不是建立多個 store;
  • redux一個特色是:狀態共享,全部的狀態都放在一個store中,任何component均可以訂閱store中的數據;
  • 並非全部的state都適合放在store中,這樣會讓store變得很是龐大,如某個狀態只被一個組件使用,不存在狀態共享,能夠不放在store中;

未完待續

參考

相關文章
相關標籤/搜索