React中的Redux

學習必備要點:

  1. 首先弄明白,Redux在使用React開發(fā)應(yīng)用時,起到什么作用——狀態(tài)集中管理
  2. 弄清楚Redux是如何實現(xiàn)狀態(tài)管理的——store瞧壮、action呛梆、reducer三個概念
  3. 在React中集成Redux:redux + react-redux(多了一個概念——selector)
  4. Redux調(diào)試工具:redux devtools
  5. redux相關(guān)很好用的插件:redux-saga的相關(guān)介紹

redux結(jié)構(gòu)圖

react-redux.png

其中紅色虛線部分為redux的內(nèi)部集成霞溪,不能顯示的看到。

  • action:是事件咒劲,它本質(zhì)上是JavaScript的普通對象继薛,它描述的是“發(fā)生了什么”。action由type:string和其他構(gòu)成爽待。
  • reducer是一個監(jiān)聽器损同,只有它可以改變狀態(tài)。是一個純函數(shù)鸟款,它不能修改state,所以必須是生成一個新的state膏燃。在default情況下,必須但會舊的state何什。
  • store是一個類似數(shù)據(jù)庫的存儲(或者可以叫做狀態(tài)樹)组哩,需要設(shè)計自己的數(shù)據(jù)結(jié)構(gòu)來在狀態(tài)樹中存儲自己的數(shù)據(jù)。

Redux入門

Redux簡介

Redux是一個狀態(tài)集中管理庫处渣。

安裝

npm install --save redux

附加包

多數(shù)情況下我們需要使用 React 綁定庫開發(fā)者工具伶贰。

npm install --save react-redux
npm install --save-dev redux-devtools

三大原則

單一數(shù)據(jù)源

整個應(yīng)用的state被存儲在一棵object tree中,并且這個object tree只存在于唯一一個store中罐栈。

State是只讀的

惟一改變 state 的方法就是觸發(fā) action黍衙,action 是一個用于描述已發(fā)生事件的普通對象。

使用純函數(shù)來執(zhí)行修改

為了描述action如何改變狀態(tài)樹荠诬,我們需要編寫reducers琅翻。Reducer只是一些純函數(shù),他接受先前的state和action柑贞,并返回新的state對象方椎。

react-redux.png

上圖是Redux如何實現(xiàn)狀態(tài)管理的框架,View(視圖) 可以通過store.dispatch()方法傳遞action钧嘶。 Action相當于事件模型中的事件棠众,它描述發(fā)生了什么。Reducer相當于事件模型中的監(jiān)聽器康辑,它接收一個舊的狀態(tài)和一個action摄欲,從而處理state的更新邏輯,返回一個新的狀態(tài)疮薇,存儲到Store中胸墙。而從store-->view 的部分,則是通過mapStateToProps 這個函數(shù)來從Store中讀取狀態(tài)按咒,然后通過props屬性的方式注入到展示組件中迟隅。圖中紅色虛線部分是Redux內(nèi)部處理但骨,我們不必過多考慮這部分的實現(xiàn)。

Action

Action 是把數(shù)據(jù)從應(yīng)用傳到store的有效載荷智袭,它是store數(shù)據(jù)的唯一來源奔缠,一般來說,我們通過store.dispatch()將action傳到store吼野。

Action創(chuàng)建函數(shù)

Action 創(chuàng)建函數(shù) 就是生成 action 的方法校哎。“action” 和 “action 創(chuàng)建函數(shù)” 這兩個概念很容易混在一起瞳步,使用時最好注意區(qū)分闷哆。

Redux中action創(chuàng)建函數(shù)只是簡單返回一個action。

改變userName的示例:

export function changeUserName(userName) {  // action創(chuàng)建函數(shù)
    return {                             // 返回一個action
        type: 'CHANGE_USERNAME',
        payload: userName,
    };
}

Action 本質(zhì)上是JavaScript 普通對象单起。我們規(guī)定抱怔,action 內(nèi)必須使用一個字符串類型的 type 字段來表示將要執(zhí)行的動作。多數(shù)情況下嘀倒,type 會被定義成字符串常量屈留。當應(yīng)用規(guī)模越來越大時,建議使用單獨的模塊或文件來存放 action测蘑。

除了 type 字段外灌危,action 對象的結(jié)構(gòu)完全由你自己決定。參照 Flux 標準 Action 獲取關(guān)于如何構(gòu)造 action 的建議碳胳,另外還需要注意的是乍狐,我們應(yīng)該盡量減少在action中傳遞數(shù)據(jù)

Reducer

Action只是描述有事情發(fā)生這一事實固逗,而Reducer用來描述應(yīng)用是如何更新state。

設(shè)計State結(jié)構(gòu)

在 Redux 應(yīng)用中藕帜,所有的 state 都被保存在一個單一對象中烫罩。在寫代碼之前我們首先要想清楚這個對象的結(jié)構(gòu),要用最簡單的形式把應(yīng)用中的state用對象描述出來洽故。

HelloApp應(yīng)用的state結(jié)構(gòu)很簡單贝攒,只需要保存userName即可:

{userName: 'World'}
處理 Reducer 關(guān)系時的注意事項

開發(fā)復(fù)雜的應(yīng)用時,不可避免會有一些數(shù)據(jù)相互引用时甚。建議你盡可能地把 state 范式化隘弊,不存在嵌套。把所有數(shù)據(jù)放到一個對象里荒适,每個數(shù)據(jù)以 ID 為主鍵梨熙,不同實體或列表間通過 ID 相互引用數(shù)據(jù)。把應(yīng)用的 state 想像成數(shù)據(jù)庫刀诬。這種方法在 normalizr 文檔里有詳細闡述

Action處理

確定了 state 對象的結(jié)構(gòu)咽扇,就可以開始開發(fā) reducer。reducer 就是一個純函數(shù),接收舊的 state 和 action质欲,返回新的 state树埠。

(state, action) => newState

之所以稱作 reducer 是因為它將被傳遞給 Array.prototype.reduce(reducer, ?initialValue) 方法。保持 reducer 純凈非常重要嘶伟。永遠不要在 reducer 里做以下操作:

  • 修改傳入?yún)?shù)怎憋;
  • 執(zhí)行有副作用的操作,如 API 請求和路由跳轉(zhuǎn)九昧;
  • 調(diào)用非純函數(shù)绊袋,如 Date.now()Math.random()

在后續(xù)的學習終將會介紹如何執(zhí)行有副作用的操作耽装,現(xiàn)在只需謹記reducer一定要保持純凈愤炸。只要傳入?yún)?shù)相同,返回計算得到的下一個 state 就一定相同掉奄。沒有特殊情況规个、沒有副作用,沒有 API 請求姓建、沒有變量修改诞仓,單純執(zhí)行計算。

我們將寫一個reducer速兔,讓它來處理之前定義過的action墅拭。我們可以首先指定state的初始狀態(tài)。

const initState = {       /** 指定初始狀態(tài) */
    userName: 'World!'
}

export default function helloAppReducer(state=initState, action) {
    switch(action.type) {
        case 'CHANGE_USERNAME':
            return {
                userName: action.payload,   // 改變狀態(tài)
            };
        default:
            return state;    // 返回舊狀態(tài)
    }
}

警告:

  1. 不要修改state涣狗。如果涉及多個狀態(tài)時谍婉,可以采用對象展開運算符的支持,來返回一個新的狀態(tài)镀钓。 假設(shè)我們的實例中還存在其它狀態(tài)穗熬,但是我們只需要改變userName的值,那么上述示例我們可以采用以下方式返回新的狀態(tài):

    return {
      ...state,
      userName: action.payload
    }
    
  2. 在default情況下返回舊的state丁溅。 遇到未知的action時唤蔗,一定要返回舊的state

Reducer拆分

這里我們以redux中文文檔 中的todo應(yīng)用為例來說明窟赏,在應(yīng)用的需求中妓柜,有添加todo項,設(shè)置todo列表的過濾條件等多個action涯穷,同理我們就需要寫多個reducer來描述狀態(tài)是怎么改變的棍掐,建議把todo列表的更新和設(shè)置過濾條件放在兩個reducer中去實現(xiàn):

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 {
            ...todo,
            completed: !todo.completed
          }
        }
        return todo
      })
    default:
      return state
  }
}

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

todos 依舊接收 state,但它變成了一個數(shù)組拷况!現(xiàn)在 todoApp 只把需要更新的一部分 state 傳給 todos 函數(shù)塌衰,todos 函數(shù)自己確定如何更新這部分數(shù)據(jù)诉稍。這就是所謂的 reducer 合成,它是開發(fā) Redux 應(yīng)用最基礎(chǔ)的模式最疆。

現(xiàn)在我們可以開發(fā)一個函數(shù)來做為主 reducer杯巨,它調(diào)用多個子 reducer 分別處理 state 中的一部分數(shù)據(jù),然后再把這些數(shù)據(jù)合成一個大的單一對象努酸。主 reducer 并不需要設(shè)置初始化時完整的 state服爷。初始時,如果傳入 undefined, 子 reducer 將負責返回它們的默認值获诈。這個過程就是reducer合并仍源。

下面的這段代碼是reducer合并的兩種方式:

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

每個 reducer 只負責管理全局 state 中它負責的一部分。每個 reducer 的 state 參數(shù)都不同舔涎,分別對應(yīng)它管理的那部分 state 數(shù)據(jù).

import { combineReducers } from 'redux';

const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp;

combineReducers() 所做的只是生成一個函數(shù)笼踩,這個函數(shù)來調(diào)用你的一系列 reducer,每個 reducer 篩選出 state 中的一部分數(shù)據(jù)并處理亡嫌,然后這個生成的函數(shù)再將所有 reducer 的結(jié)果合并成一個大的對象嚎于。

Store

前面的部分,我們學會使用action來描述發(fā)生了什么挟冠,使用reducers來根據(jù)action更新state的用法于购。

Store則是把action和reducers聯(lián)系到一起的對象,它有以下職責:

再次說明Redux應(yīng)用只有一個單一的store嫌吠。 當需要拆分處理數(shù)據(jù)邏輯時,我們應(yīng)該使用 reducer 組合 而不是創(chuàng)建多個 store掺炭。

根據(jù)已有的reducer來創(chuàng)建store是非常容易的居兆。在我們的HelloApp應(yīng)用中,我們將helloAppReducer 導(dǎo)入竹伸,并傳遞給createStore()

import { createStore } from 'redux'
import helloAppReducer from './reducers'

let store = createStore(helloAppReducer)   // 創(chuàng)建store

createStore() 的第二個參數(shù)是可選的, 用于設(shè)置 state 初始狀態(tài)簇宽。

備注:
其實這種數(shù)據(jù)結(jié)構(gòu)是有reducer確定的勋篓,就像helloAPP的例子中,

const reducer = combineReducers({
  hello: hello,
  city: cityReducer
})

而由redux-devtools工具查看到的是下圖這樣的:

store-tree.png

so魏割,存儲在store中的數(shù)據(jù)結(jié)構(gòu)是由reducer確定的譬嚣。

數(shù)據(jù)流

嚴格的單向數(shù)據(jù)流 是Redux架構(gòu)的核心設(shè)計。這就意味著應(yīng)用中所有的數(shù)據(jù)都遵循相同的生命周期钞它,這樣可以讓應(yīng)用變得更加可預(yù)測且容易理解拜银。同時也鼓勵做數(shù)據(jù)范式化殊鞭,這樣可以避免使用多個且獨立的無法相互引用的重復(fù)數(shù)據(jù)。

Redux應(yīng)用中數(shù)據(jù)的生命周期遵循以下4個步驟:

  1. 調(diào)用store.dispatch(action) 尼桶。

    Action 就是一個描述“發(fā)生了什么”的普通對象操灿。比如:

    { type: 'CHANGE_USERNAME', payload: "Welcome to Redux" };
    

    我們可以在任何地方調(diào)用store.dispatch(action) 包括組件中、XHR回調(diào)中泵督、甚至是定時器中趾盐。

  2. Redux store 調(diào)用傳入的 reducer 函數(shù)。

    Store 會把兩個參數(shù)傳入 reducer: 當前的 state 樹和 action小腊。

    const initState = {       /** 指定初始狀態(tài) */
        userName: 'World!'
    }
    
    export default function helloAppReducer(state=initState, action) {  // 傳入兩個參數(shù)
        switch(action.type) {
            case 'CHANGE_USERNAME':
                return {
                    userName: action.payload,   // 改變狀態(tài)
                };
            default:
                return state;    // 返回當前狀態(tài)
        }
    }
    

    reducer 是純函數(shù)救鲤。它僅僅用于計算下一個 state。它應(yīng)該是完全可預(yù)測的:多次傳入相同的輸入必須產(chǎn)生相同的輸出秩冈。它不應(yīng)做有副作用的操作本缠,如 API 調(diào)用或路由跳轉(zhuǎn)。這些應(yīng)該在 dispatch action 前發(fā)生入问。

  3. 根 reducer 應(yīng)該把多個子 reducer 輸出合并成一個單一的 state 樹丹锹。

    根 reducer 的結(jié)構(gòu)完全由我們自己決定。Redux 原生提供combineReducers()輔助函數(shù)队他,來把根 reducer 拆分成多個函數(shù)卷仑,用于分別處理 state 樹的一個分支。

  4. Redux store 保存了根 reducer 返回的完整 state 樹麸折。

    這個新的樹就是應(yīng)用的下一個state锡凝。所有訂閱store.subscribe(listener) 的監(jiān)聽器都將被調(diào)用;監(jiān)聽器里可以調(diào)用store.getState() 獲取當前的state垢啼。

示例: Hello App

如果想查看示例的源碼窜锯,請查看這里。Hello App源碼
開始之前我們需要清楚實際上Redux和React之間并沒有關(guān)系芭析。Redux支持React锚扎、Angular、Ember馁启、jQuery甚至純JavaScript驾孔。即便如此,Redux 還是和 ReactDeku 這類框架搭配起來用最好惯疙,因為這類框架允許你以 state 函數(shù)的形式來描述界面翠勉,Redux 通過 action 的形式來發(fā)起 state 變化。

下面我們將用React來開發(fā)一個Hello World的簡單應(yīng)用霉颠。

安裝React Redux

Redux默認并不包含 React 綁定庫对碌,需要單獨安裝。

npm install --save react-redux

容器組件和展示組件

Redux 的 React 綁定庫是基于 容器組件和展示組件相分離 的開發(fā)思想蒿偎。而容器組件和展示組件大致有以下不同:

展示組件 容器組件
作用 描述如何展現(xiàn)內(nèi)容朽们、樣式 描述如何運行(數(shù)據(jù)獲取怀读、狀態(tài)更新)
是否能直接使用Redux
數(shù)據(jù)來源 props(屬性) 監(jiān)聽Redux state
數(shù)據(jù)修改 從props中調(diào)用回調(diào)函數(shù) 向Redux派發(fā)actions
調(diào)用方式 手動 通常由React Redux生成

大部分的組件都應(yīng)該是展示型的,但一般需要少數(shù)的幾個容器組件把它們和Redux store連接起來骑脱。

技術(shù)上來說我們可以直接使用 store.subscribe() 來編寫容器組件菜枷。但不建議這么做,因為這樣寫就無法使用 React Redux 帶來的性能優(yōu)化惜姐。同樣犁跪,不要手寫容器組件,我們直接使用 React Redux 的 connect() 方法來生成歹袁,后面會詳細介紹坷衍。

需求分析

我們的需求很簡單,我們只是想要展示hello + userName,默認為“Hello World!”,當我們在輸入框中輸入不同的值時条舔,會顯示不同的“hello枫耳,___”問候語,由此可以分析出該應(yīng)用只有一個狀態(tài)孟抗,那就是{ userName: '張三'}

展示組件

該應(yīng)用只有一個展示組件HelloPanel:

  • HelloPanel 用于顯示輸入框及展示數(shù)據(jù)
    • userName: 要展示的數(shù)據(jù)
    • onChange(userName) : 當輸入值發(fā)生變化時調(diào)用的回調(diào)函數(shù)

該組件之定義外觀并不涉及數(shù)據(jù)從哪里來迁杨,如果改變它,傳入什么就渲染什么凄硼,如果你把代碼從Redux遷移到別的架構(gòu)铅协,該組件可以不做任何改動直接使用。

容器組件

還需要一個容器組件來把展示組件連接到Redux摊沉。例如HelloPanel 組件需要一個狀態(tài)類似HelloApp的容器來監(jiān)聽Redux store變化并處理如何過濾出要展示的數(shù)據(jù)狐史。

HelloApp 根據(jù)當前顯示狀態(tài)來對展示組件進行渲染。

組件編碼

  • Action創(chuàng)建函數(shù)

    action.js

    export function changeUserName(userName) {
        return {
            type: 'CHANGE_USERNAME',
            payload: userName,
        };
    }
    
  • Reducer

    index.js

    const initState = {       /** 指定初始狀態(tài) */
        userName: 'World!'
    }
    
    export default function helloAppReducer(state=initState, action) {
        switch(action.type) {
            case 'CHANGE_USERNAME':
                return {
                    userName: action.payload,   // 改變狀態(tài)
                };
            default:
                return state;    // 返回當前狀態(tài)
        }
    }
    
  • 展示組件

    HelloPanel.js

    import React from 'react';
    
    export default function HelloPanel(props) {
      let input
      return (
        <div>
        <p>Hello, {props.userName}</p>
        <input ref={node => {
              input = node
            }}  onChange={()=>props.onChange(input.value)}/>
        </div>
      );
    }
    
  • 容器組件

    使用 connect() 創(chuàng)建容器組件前说墨,需要先定義 mapStateToProps 這個函數(shù)來指定如何把當前 Redux store state 映射到展示組件的 props 中骏全。例如:HelloApp 中需要計算

    const mapStateToProps = (state) => {
        return { userName: state.userName }  // 返回期望注入到展示組件的props中的參數(shù)
    };
    

    除了讀取state,容器組件還能分發(fā)action。類似的方式尼斧,可以定義 mapDispatchToProps() 方法接收 dispatch() 方法并返回期望注入到展示組件的 props 中的回調(diào)方法姜贡。

    const mapDispatchToProps = (dispatch) => ({
        onChange: (userName) => {
            dispatch(changeUserName(userName))  // 返回期望注入到展示組件的 props 中的回調(diào)方法
        }
    })
    

    最后,使用 connect() 創(chuàng)建 HelloApp棺棵,并傳入這兩個函數(shù)楼咳。

    import { connect } from 'react-redux';
    import HelloPanel from './HelloPanel';
    
    const HelloApp = connect(  // 產(chǎn)生一個新的組件
        mapStateToProps,
        mapDispatchToProps,
    )(HelloPanel)
    
    

    這就是 React Redux API 的基礎(chǔ),但還漏了一些快捷技巧和強大的配置烛恤。建議仔細學習 React Redux文檔母怜。如果你擔心 mapStateToProps 創(chuàng)建新對象太過頻繁,可以學習如何使用 reselect計算衍生數(shù)據(jù)棒动。

傳入Store

所有容器組件都可以訪問 Redux store,所以可以手動監(jiān)聽它宾添。一種方式是把它以 props 的形式傳入到所有容器組件中船惨。但這太麻煩了柜裸,因此必須要用 store 把展示組件包裹一層,恰好在組件樹中渲染了一個容器組件粱锐。

建議的方式是使用指定的 React Redux 組件 <Provider> 來讓所有容器組件都可以訪問 store疙挺,而不必顯示地傳遞它。只需要在渲染根組件時使用即可怜浅。

import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import HelloApp from './HelloApp'
import HelloReducer from './reducers'

let store = createStore(HelloReducer)

render(
  <Provider store={store}>
    <HelloApp />
  </Provider>,
  document.getElementById('root')
)

到這里铐然,我們已經(jīng)基本掌握了Redux的基礎(chǔ)及核心概念,有了這些恶座,我們就可以開發(fā)簡單的應(yīng)用搀暑,關(guān)于Redux的更多實例、高級應(yīng)用跨琳、技巧自点、API文檔等可以查看redux中文文檔

子狀態(tài)樹與combineReducers(reducers)

簡介

隨著應(yīng)用變得復(fù)雜脉让,需要對 reducer 函數(shù) 進行拆分桂敛,拆分后的每一塊獨立負責管理 state 的一部分。

combineReducers 輔助函數(shù)的作用是溅潜,把一個由多個不同 reducer 函數(shù)作為 value 的 object术唬,合并成一個最終的 reducer 函數(shù),然后就可以對這個 reducer 調(diào)用 createStore滚澜。

合并后的 reducer 可以調(diào)用各個子 reducer粗仓,并把它們的結(jié)果合并成一個 state 對象。state 對象的結(jié)構(gòu)由傳入的多個 reducer 的 key 決定博秫。

最終潦牛,state 對象的結(jié)構(gòu)會是這樣的:

{
  reducer1: ...
  reducer2: ...
}

使用:

combineReducers({
  hello, cityReducer
})

state 對象的結(jié)構(gòu):

// 實際例子
{
  "hello":{"userName":"張三"}, 
  "cityReducer":{"city":"北京"}
}

通過為傳入對象的 reducer 命名不同來控制 state key 的命名。

e.g.:

你可以調(diào)用 combineReducers({hello: hello,city: cityReducer}) 將 state 結(jié)構(gòu)變?yōu)?code>{ hello, city }

通常的做法是命名 reducer挡育,然后 state 再去分割那些信息巴碗,因此你可以使用 ES6 的簡寫方法:combineReducers({ hello, city })。這與 combineReducers({ hello: hello,city: cityReducer }) 一樣即寒。

對于reducer的結(jié)構(gòu)橡淆,我們規(guī)定只能是一級的,也就是

{
  "hello":{"userName":"張三"}, 
  "cityReducer":{"city":"北京"}
}

這種結(jié)構(gòu)母赵,不能再有子樹逸爵,這樣是為了方便進行管理。

參數(shù)

reducers (Object)是一個對象凹嘲,它的值(value) 對應(yīng)不同的 reducer 函數(shù)师倔,這些 reducer 函數(shù)后面會被合并成一個。下面會介紹傳入 reducer 函數(shù)需要滿足的規(guī)則周蹭。

之前的文檔曾建議使用 ES6 的 import * as reducers 語法來獲得 reducer 對象趋艘。這一點造成了很多疑問疲恢,因此現(xiàn)在建議在 reducers/index.js 里使用 combineReducers() 來對外輸出一個 reducer。下面有示例說明瓷胧。

返回值

(Function):一個調(diào)用 reducers 對象里所有 reducer 的 reducer显拳,并且構(gòu)造一個與 reducers 對象結(jié)構(gòu)相同的 state 對象。

注意

本函數(shù)設(shè)計的時候有點偏主觀搓萧,就是為了避免新手犯一些常見錯誤杂数。也因些我們故意設(shè)定一些規(guī)則,但如果你自己手動編寫根 redcuer 時并不需要遵守這些規(guī)則瘸洛。

每個傳入 combineReducers 的 reducer 都需滿足以下規(guī)則:

  • 所有未匹配到的 action揍移,必須把它接收到的第一個參數(shù)也就是那個 state 原封不動返回。
  • 永遠不能返回 undefined货矮。當過早 return 時非常容易犯這個錯誤羊精,為了避免錯誤擴散,遇到這種情況時 combineReducers 會拋異常囚玫。
  • 如果傳入的 state 就是 undefined喧锦,一定要返回對應(yīng) reducer 的初始 state。根據(jù)上一條規(guī)則抓督,初始 state 禁止使用 undefined燃少。使用 ES6 的默認參數(shù)值語法來設(shè)置初始 state 很容易,但你也可以手動檢查第一個參數(shù)是否為 undefined铃在。

實例:

const hello = (state = {userName: 'Hehe'}, action) => { // 設(shè)置了初始值
  switch (action.type) {
    case 'USER_CHANGE':
      return {
        userName: action.userName
      }
    // 所有未匹配到的 action阵具,必須把它接收到的第一個參數(shù)也就是那個 state 原封不動返回。
    default: 
      return state
  }
}

export default hello

異步action

學習到這里定铜,我們所接觸的下圖上的所有實現(xiàn)阳液,都是針對同步事件的。如果只是這樣揣炕,那么我們肯定不能放心大膽的使用redux在我們的項目中帘皿,因為我們實際項目中,更多的都是異步事件畸陡。所以接下來鹰溜,讓我們來介紹一個復(fù)雜的場景,我們來看看redux是如何應(yīng)用在大型復(fù)雜充滿異步事件的場景中的丁恭。

react-redux.png

我們?nèi)匀粫袷厣蠄D曹动,這是我們的核心,不能改變牲览,下面我們來看一個實際的例子墓陈,工資列表頁面。

工資列表頁面

也就是一個普通的通過網(wǎng)絡(luò)請求,去請求列表數(shù)據(jù)的列表的展示贡必。我們先來分析一下狀態(tài)熬的,列表頁面的狀態(tài)。

狀態(tài)(state)

是一種數(shù)據(jù)結(jié)構(gòu)赊级,存儲在store中的數(shù)據(jù)

異步加載的頁面的狀態(tài):“加載中;加載成功岔绸,展示列表理逊;加載失敗” 這三種狀態(tài)。我們給這三種狀態(tài)來取一個名字盒揉,并設(shè)置0晋被,1,2來順序表示不同的狀態(tài)刚盈。

loadingListStatus:0|1|2

我們主要做的是列表頁的展示羡洛,那么還有一個最重要的數(shù)據(jù)結(jié)構(gòu)就是列表數(shù)據(jù),我們來取一個名字:

salaryList:[]

接下來我們再來分析一下藕漱,action欲侮,也就是事件。

事件

列表展示過程中的數(shù)據(jù)肋联,也就是:“開始加載威蕉;加載成功;加載失敗”這三個事件橄仍。其實整個過程和之前使用promise來實現(xiàn)的異步操作是一樣的韧涨。我們是監(jiān)聽action,然后產(chǎn)生異步操作侮繁,執(zhí)行dispatch方法虑粥,將數(shù)據(jù)結(jié)構(gòu)保存到store中。

例子

我們來看一個獲取列表的請求:

function fetchSalayList(subreddit) {
  return dispatch => {
    dispatch(loadingAction(subreddit))// 開始加載
    return fetch(`http://www.reddit.com/r/${subreddit}.json`)
      .then(response => response.json())
      .then(json => { // 加載成功
        dispatch(loadingSucessAction(subreddit, json))
      }, (error) => { // 加載失敗
        dispatch(loadingErroeAction(subreddit))
      }
  }
}

上述這種方式宪哩,完全符合我們的核心圖表娩贷,并且實現(xiàn)了異步操作。

在異步操作這塊斋射,我們建議使用 redux-saga 中間件來創(chuàng)建更加復(fù)雜的異步 action育勺。其中涉及到es6中的Generators可以在文檔中查看。另外罗岖,還有 redux-saga的使用的一個例子可以看這里涧至。

異步數(shù)據(jù)流

默認情況下,createStore() 所創(chuàng)建的 Redux store 沒有使用 middleware桑包,所以只支持 同步數(shù)據(jù)流南蓬。

你可以使用 applyMiddleware() 來增強 createStore()。雖然這不是必須的,但是它可以幫助你用簡便的方式來描述異步的 action赘方。

redux-thunkredux-promise 這樣支持異步的 middleware 都包裝了 store 的 dispatch() 方法烧颖,以此來讓你 dispatch 一些除了 action 以外的其他內(nèi)容,例如:函數(shù)或者 Promise窄陡。你所使用的任何 middleware 都可以以自己的方式解析你 dispatch 的任何內(nèi)容炕淮,并繼續(xù)傳遞 actions 給下一個 middleware。比如跳夭,支持 Promise 的 middleware 能夠攔截 Promise涂圆,然后為每個 Promise 異步地 dispatch 一對 begin/end actions。

當 middleware 鏈中的最后一個 middleware 開始 dispatch action 時币叹,這個 action 必須是一個普通對象润歉。這是 同步式的 Redux 數(shù)據(jù)流 開始的地方(譯注:這里應(yīng)該是指,你可以使用任意多異步的 middleware 去做你想做的事情颈抚,但是需要使用普通對象作為最后一個被 dispatch 的 action 踩衩,來將處理流程帶回同步方式)。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末贩汉,一起剝皮案震驚了整個濱河市驱富,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌匹舞,老刑警劉巖萌朱,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異策菜,居然都是意外死亡晶疼,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進店門又憨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來翠霍,“玉大人,你說我怎么就攤上這事蠢莺『祝” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵躏将,是天一觀的道長锄弱。 經(jīng)常有香客問我,道長祸憋,這世上最難降的妖魔是什么会宪? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮蚯窥,結(jié)果婚禮上掸鹅,老公的妹妹穿的比我還像新娘塞帐。我一直安慰自己,他們只是感情好巍沙,可當我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布葵姥。 她就那樣靜靜地躺著,像睡著了一般句携。 火紅的嫁衣襯著肌膚如雪榔幸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天矮嫉,我揣著相機與錄音牡辽,去河邊找鬼。 笑死敞临,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的麸澜。 我是一名探鬼主播挺尿,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼炊邦!你這毒婦竟也來了编矾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤馁害,失蹤者是張志新(化名)和其女友劉穎窄俏,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碘菜,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡凹蜈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了忍啸。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仰坦。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖计雌,靈堂內(nèi)的尸體忽然破棺而出悄晃,到底是詐尸還是另有隱情,我是刑警寧澤凿滤,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布妈橄,位于F島的核電站,受9級特大地震影響翁脆,放射性物質(zhì)發(fā)生泄漏眷蚓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一反番、第九天 我趴在偏房一處隱蔽的房頂上張望溪椎。 院中可真熱鬧普舆,春花似錦、人聲如沸校读。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽歉秫。三九已至蛾洛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間雁芙,已是汗流浹背轧膘。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留兔甘,地道東北人谎碍。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓脂男,卻偏偏與公主長得像灾锯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子若债,可洞房花燭夜當晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容