Redux is a predictable state container for JavaScript apps
簡單來講,Redux
是一個管理應用狀態的框架javascript
前端開發中,本質的問題就是將 server -> client 的輸入,變成 client -> user 輸入;再將 user -> client 的輸入,變成 client -> server 的輸入。html
在 client 中,前端的角色其實大概能夠當作一個"轉換器"。前端
舉個簡單的例子,後端傳過來的是一個 json 格式的數據,這個 json
格式,實際上是在計算機範疇內的,真正的終端用戶並不知道什麼是json
,更不知道要如何修改json
,保存本身的信息。因此,這個時候就須要像上面說的把 json
轉換爲頁面上的內容和元素;另外,隨着用戶的一系列操做,數據須要隨時更新保存到服務端。整個的這個過程,可能會很複雜,數據和數據之間會存在聯動關係。java
這個時候,就須要有一個東西,從更高的層面來管理全部的這些狀態,因而有了 mvc
,狀態保存在model
,數據展現在view
,controller
來串聯用戶的輸入和數據的更新。可是這個時候就會有個問題,理想狀況下,咱們默認全部的狀態更新都是由用戶的操做(也能夠理解爲用戶的輸入)來觸發的,但實際狀況中,會觸發狀態更新的不只僅是單純的用戶操做,還有多是用戶操做帶來的後果,在舉個例子:react
頁面上有個異步獲取信息
的按鈕,用戶能夠點擊這個按鈕,那麼用戶點擊這個按鈕以後,會發生:git
按鈕狀態變爲 pending --> 獲取成功,按鈕狀態變成 success | |--> 獲取失敗,按鈕狀態變成 error
這裏改變success/error
狀態的並非用戶輸入,而是服務端的返回,這個時候,就須要在 controller
裏面 handle 服務端的返回。這只是個簡單的例子,若是相似的狀況發生了不少以後,每次輸入和輸出將變得難以預測,難以預測的後果就是很容易出現 bug,程序的健壯性降低。github
讓每一步輸入和輸出可預測,可預測才能可測試,可測試才能保證健壯性。json
因而,這個時候出現了React
和Flux
。redux
Flux
的核心思想就是維護一個單向數據流,數據的流向永遠是單向的,因此每一個步驟即是可預測的,程序的健壯性獲得了保證。後端
React
的 jsx
能夠將前端的 UI 部分變成了一層層套用的方法,再舉個例子,以前寫 html 是這樣的
<div> <span>foo</span> </div>
若是狀態改變以後,大部分狀況下咱們是將某個片斷的 html 用改變的狀態從新拼一遍,而後替換到原有的 dom 結構裏。
可是,用了 jsx
以後,你的代碼將變成這樣:
div(span('foo'))
變成了一個函數,這個函數的輸出就是上面的那段 html,因此整個 UI 變成了一個可輸入輸出的結構,有了輸入和輸出,就是一個完整的可預測的結構了,可預測,也就是表明可測試了。
在使用Flux
的過程裏,當應用的結構變得複雜以後,會顯得力不從心,雖然數據流仍是單向,可是Flux
的總體流程有兩個比較關鍵的點:
emit
handle emit
當數據結構和輸入輸出變得複雜的時候,每每會定義不少個 store,可是每每 store 之間仍是會有依賴和關聯。
這個時候,handle 的過程會變得很臃腫,難以理解。
而後,Redux
就出場了。
Flux
的思路能夠理解爲多個store
組成了一個完整的 App;Redux
的思路則是一個完整的store
對應一個完整的 App。
Redux
相比Flux
,多抽象出了一個reducer
的概念。這個reducer
只負責狀態的更新,而且會返回一個新的狀態對象,整個 App 從結構上看起來,沒有一個一直保存/更新的狀態(使用Flux
每一個store
都是一直保存住的,而後在此基礎上進行更新),Redux
中的數據更像是一個流程。
另外,還有一點比較重要的是,由於沒有了一個一直保存/更新的狀態對象,因此在 component
中的 handle
也就沒有意義了,經過react-redux
能夠徹底實現一個順暢的數據流。
這裏舉個簡單的例子,若是咱們更新一個訂單,訂單裏有這麼幾項:
其中地址影響運費,運費影響總價;另外,商品數量也會影響總價
使用Flux
的話,咱們一般會分解成這樣幾個store
:
其中 address
和items
的更新會觸發deal.amount
的更新,完整的交易信息會同步到deal
中。
在component
裏,咱們會handel
全部這些store
的emit
,而後再進行setState
以更新 UI 部分。
使用Redux
的話,咱們會分解成這樣幾個reducer
:
其中address
只負責address
的更新,item
只負責items
的更新,deal
會響應address
和item
中跟交易相關的更新,實現改變價格和訂單地址的操做。
可是並不須要在component
中再hanle
每一個部分更新以後的emit
。數據更新了,頁面就會本身變化。
接下來,咱們看看Redux
是如何實現的。
查看Redux
的github,會發現Redux
的代碼異常的精簡,僅僅包含了這幾個部分:
其中的utils/
和index.js
咱們並不須要關心,只要看接下來的幾部分就能夠。
另外,由於咱們的大部分場景仍是搭配React
來使用Redux
,因此這裏咱們順便搭配 react-redux
來看下
在react-redux
中,咱們關心的更少,只有:
這兩部分而已。
拿一個真正的實例來看,咱們要作一個簡單的訂單,目錄結構是這樣的:
|- dealReducer.js |- dealActions.js |- dealStore.js |- dealApp.js |- main.js
先看代碼:
import React from 'react' import ReactDom from 'react-dom' import { Provider } from 'react-redux' import configureStore from './dealStore' import DealApp from './dealApp' let store = configureStore() ReactDom.render( ( <Provider store={ store }> <DealApp /> </Provider> ), document.getElementById('app'))
這個部分比較簡單,首先是調用了dealStore
中的方法,生成了一個store
,而後調用了react-redux
中的Provider
把這個store
綁定到了Provider
上。
咱們先看 Provider
的代碼:
咱們只看下核心的部分:
export default class Provider extends Component { getChildContext() { return { store: this.store } } constructor(props, context) { super(props, context) this.store = props.store } render() { return Children.only(this.props.children) } }
其實最核心就是getChildContext
方法,這個方法在每次props
和state
被調用時會被觸發,這裏更新了store
仍是先看代碼:
import React, { Component, PropTypes } from 'react' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import * as dealActions from 'deal/actions/dealActions' import * as addressActions from 'deal/actions/addressActions' class DealApp extends Component { // some code } function mapStateToProps(state) { return { 'deal': state.dealReducer, 'address': state.addressReducer, } } function mapDispatchToProps(dispatch) { return { 'dealActions': bindActionCreators(dealActions, dispatch), 'addressActions': bindActionCreators(addressActions, dispatch), } } export default connect(mapStateToProps, mapDispatchToProps)(DealApp)
從代碼能夠看到,比通常的 react component
多了對connect
的調用,以及mapStateToProps
和mapDispatchToProps
兩個方法。
因此,接下來看下這個connect
是什麼
來看下核心部分的代碼:
// some code componentDidMount() { this.trySubscribe() }, trySubscribe() { if (shouldSubscribe && !this.unsubscribe) { this.unsubscribe = this.store.subscribe(this.handleChange.bind(this)) this.handleChange() } }, handleChange() { if (!this.unsubscribe) { return } const storeState = this.store.getState() const prevStoreState = this.state.storeState if (pure && prevStoreState === storeState) { return } if (pure && !this.doStatePropsDependOnOwnProps) { const haveStatePropsChanged = tryCatch(this.updateStatePropsIfNeeded, this) if (!haveStatePropsChanged) { return } if (haveStatePropsChanged === errorObject) { this.statePropsPrecalculationError = errorObject.value } this.haveStatePropsBeenPrecalculated = true } this.hasStoreStateChanged = true this.setState({ storeState }) }
能夠看到,這幾個方法用到了store
中的getState
和subscribe
這幾個方法。而且在handleChange
中,實現了在Flux
中須要人肉實現的setState
方法。
既然在上面的connect
中,用到了store
,那麼就來看看dealStore
的內容:
import { createStore, applyMiddleware, compose } from 'redux' import thunk from 'redux-thunk' import dealReducers from 'deal/reducers/dealReducer' let creator = compose( applyMiddleware(thunk), applyMiddleware(address), )(createStore) export default function configureStore(initState) { const store = creator(dealReducers, initState) return store }
這個文件裏用到了redux
中的createStore
, compose
和applyMiddleware
方法。
經過調用能夠看到,先是經過applyMiddleware
方法調用了一些middleware
,而後再用compose
將對middleware
的調用串聯起來,返回一個方法,先簡單列爲f(createStore)
,而後這個調用再次返回了一個方法,這裏被定義爲creator
。經過調用creator
方法,最終生成了 store
。
下面逐個看一下createStore
,compose
,applyMiddleware
這幾個方法。
直接看源碼:
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer) var dispatch = store.dispatch var chain = [] var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
這裏直接返回了一個接收createStore
做爲參數的方法,這個方法中會遍歷傳入的middleware
,並使用compose
調用store.dispatch
,接下來看一下compose
方法的具體實現。
仍是直接貼源碼:
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) }
能夠看到 compose
的源碼十分精簡,整個compose
的做用就是傳入一串funcs
,而後返回一個方法,先暫定這個方法名爲c
,c
將傳入的funcs
按照從右到左的順序,逐個執行c
傳入的參數。
爲何要按照從右到左的順序執行,咱們先按下不表,接下來看 createStore
的源碼。
createStore
的源碼比較長,這裏就不貼了,詳情能夠見這裏。
咱們這裏只看下這個方法的輸入和輸出既可:
export default function createStore(reducer, preloadedState, enhancer) { // code return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
輸入有三個,reducer
和preloadState
咱們都屬性,可是這個enhancer
是什麼呢?
再來看下相關代碼:
if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } return enhancer(createStore)(reducer, preloadedState) }
enhancer
能夠當作是預先設定的,對createStore
返回對象執行的方法,好比能夠給返回的對象添加一些新的屬性或者方法之類的操做,就能夠放到enhancer
中作。
看到這裏,咱們再來看下compose
中爲何調用reducerRight
,將方法從右至左執行。
首先,是applyMiddleware
方法獲取到傳入的createStore
,返回了:
{ ...store, dispatch }
可是這裏的dispatch
已經不是creatStore
中返回的store.dispatch
了。這個dispatch
是經過調用compose
將store.dispatch
傳入middlewares
中執行的結果。
再回到主線上來,applyMiddleware
返回了一個加強的store
,若是有多個applyMiddleware
的調用,以下所示:
compose( applyMiddleware(A), applyMiddleware(B), applyMiddleware(C) )
咱們的指望的執行順序固然是A,B,C
這樣,因此轉換成方法的話,應該是這樣
C(B(A()))
使用reducerRight
的話,最早被調用的方法(也就是上面的C
)就會是執行鏈的最外層的方法,因此要按照從右到左的順序執行。
至此,Redux
的介紹就先到這裏,以後會再寫一些關於Redux
周邊組件的使用。