
Redux這個(gè)npm包,提供若干API讓我們使用reducer創(chuàng)建store,并能更新store中的數(shù)據(jù)或獲取store中最新的狀態(tài)?!癛edux應(yīng)用”指使用redux結(jié)合視圖層實(shí)現(xiàn)(React)及其他前端應(yīng)用必備組件(路由庫、Ajax請(qǐng)求庫)組成的完成類Flux思想的前端應(yīng)用。
Redux 三大原則
單一數(shù)據(jù)源
Redux思想中,一個(gè)應(yīng)用永遠(yuǎn)只有唯一的數(shù)據(jù)源。combineReducers化解了數(shù)據(jù)源對(duì)象過于龐大的問題。狀態(tài)是只讀的
Redux中,我們并不會(huì)自己用代碼來定義一個(gè)store。取而代之的是,我們定義一個(gè)rducer,它的功能是根據(jù)當(dāng)前觸發(fā)的action對(duì)當(dāng)前應(yīng)用的狀態(tài)(state)進(jìn)行迭代,這里我們斌沒有直接修改應(yīng)用的狀態(tài),而是返回一份全新的狀態(tài)。
Redux提供的createStore方法會(huì)根據(jù)reducer生成store。最后,我們利用 store.dispatch 方法來達(dá)到修改狀態(tài)的目的。狀態(tài)修改均有純函數(shù)完成
在Redux中,我們通過定義reducer來確定狀態(tài)的修改。
Redux核心API
Redux的核心是一個(gè)store,這個(gè)store由Redux提供的 createStore(reducers, [,initialState])方法生成。
Redux里,負(fù)責(zé)響應(yīng)action并修改數(shù)據(jù)的角色是reducer。其函數(shù)簽名為reducer(previousState, action ) => newState。所以,reducer的職責(zé)就是根據(jù) previousState 和 action 計(jì)算出新的 newState。
// MyReducer.js
const initialState = {
todos: [],
}
// 我們定義的todos這個(gè)reducer在第一次執(zhí)行的時(shí)候,會(huì)返回 { todos: [] }作為初始化狀態(tài)
function todos(previousState = initalState, action) {
switch(action.type) {
case 'xx': {
// 具體的業(yè)務(wù)邏輯
}
default:
return previousState;
}
}
Redux = Reducer + Flux
通過createStore方法創(chuàng)建的store是一個(gè)對(duì)象,它本身又包含4個(gè)方法
- getState():獲取store中當(dāng)前的狀態(tài)
- dispatch(action): 分發(fā)一個(gè)action,并返回這個(gè)action,這是唯一能改變 store 中數(shù)據(jù)的方式
- subscribe(listener):注冊(cè)一個(gè)監(jiān)聽者,它在store發(fā)生變化時(shí)被調(diào)用
- replaceReducer(nextReducer):更新當(dāng)前store里的reducer,一般只會(huì)在開發(fā)模式中調(diào)用該方法
與React綁定
react-redux提供了一個(gè)組件和一個(gè)API幫助Redux和React進(jìn)行綁定,一個(gè)是React組件<Provider />,一個(gè)是 connect(). <Provider />接受一個(gè)store作為props,是整個(gè)Redux應(yīng)用的頂層組件,connect()提供整個(gè)React應(yīng)用的任意組件中獲取store中數(shù)據(jù)的功能。
Redux middleware
它提供了一個(gè)分類處理action的機(jī)會(huì)。

面對(duì)多樣的業(yè)務(wù)場(chǎng)景,單純地修改 dispatch 或 reducer 的代碼顯然不具有普適性,我們需要的是可以組合的、自由插拔的插件機(jī)制,這一點(diǎn) Redux借鑒了 Koa (它是用于構(gòu)建 Web 應(yīng)用的Node.js 框架)里 middleware 的思想。

理解middleware機(jī)制
Redux提供了applyMiddleware方法來加載middleware。
Redux中的applyMiddleware源碼
import compose from './compose'
export default function applyMiddleware(...middlewares) {
return (next) => (reducer, initialState) => {
let store = next(reducer, initialState)
let dispatch = store.dispatch
let chain = []
// 把store的getState方法和dispatch方法分別直接或間接賦值給middlewareAPI
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
// 讓每個(gè)middleware帶著middlewareAPI這個(gè)參數(shù)分別執(zhí)行一遍
// 執(zhí)行完后,獲得數(shù)組 [f1,f2,f3,f4,...,fn]
// middlewareAPI第二個(gè)箭頭函數(shù)返回的匿名函數(shù),因?yàn)殚]包,每個(gè)匿名函數(shù)都可以訪問相同的store,即middlewareAPI
/*
middlewareAPI中的dispatch為什么要用匿名函數(shù)包裹呢?
我們用 applyMiddleware 是為了改造 dispatch,所以 applyMiddleware 執(zhí)行完后,dispatch 是 變化了的,而 middlewareAPI 是 applyMiddleware 執(zhí)行中分發(fā)到各個(gè) middleware 的,所以 必須用匿名函數(shù)包裹 dispatch,這樣只要 dispatch 更新了,middlewareAPI 中的 dispatch 應(yīng) 用也會(huì)發(fā)生變化。
*/
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
- 函數(shù)式編程思想設(shè)計(jì)
middleware設(shè)計(jì)的是一層層包裹的匿名函數(shù),這其實(shí)是函數(shù)式編程中的currying,他是一種私用匿名單參數(shù)函數(shù)來實(shí)現(xiàn)多參數(shù)函數(shù)的方法。
currying的middleware結(jié)構(gòu)的好處有以下兩點(diǎn):
- 易串聯(lián):currying函數(shù)具有延遲執(zhí)行的特性,通過不斷currying形成的middleware可以累積參數(shù),再配合組合(compose)的方式,很容易形成pipeline來處理數(shù)據(jù)流
- 共享store:在applyMiddleware執(zhí)行的過程中,store還是舊的,但是因?yàn)殚]包的存在,applyMiddleware完成后,所有的middleware內(nèi)部拿到的store是最新且相同的。
import { createStore, applyMiddleware, compose } from 'Redux';
import rootReducer from '../reducers';
const finalCreateStore = compose(
// 在開發(fā)環(huán)境中使用 middleware
applyMiddleware(d1, d2, d3),
DevTools.instrument()
)
給middleware分發(fā)store
let newStore = applyMiddleware(mid1,mid2,mid3,...)(createStore)(reducer, null)
看注釋。組合串聯(lián)middleware
dispatch = compose(...chain)(store.dispatch);
// compose 實(shí)現(xiàn)方式
function compose(...funs) {
return arg => func.reduceRight((composed, f) => f(composed), arg)
}
compose(...funcs)返回一個(gè)匿名函數(shù),其中funcs就是chain數(shù)組。當(dāng)調(diào)用reduceRight時(shí),依次從funcs數(shù)組的右端取一個(gè)函數(shù)fx拿來執(zhí)行,fx的參數(shù)composed就是前一次fx+1執(zhí)行的結(jié)果,而第一次執(zhí)行的fn(n代表chain的長度)的參數(shù)arg就是 store.dispath.假設(shè)n=3
dispatch = f1(f2(f3(store.dispatch)))
這時(shí)調(diào)用新dispatch時(shí),每一個(gè)middleware都依次執(zhí)行了。
- 在middleware中調(diào)用dispatch會(huì)發(fā)聲什么
compose之后,所有的middleware算是串聯(lián)起來了??墒沁€有一個(gè)問題,在分發(fā)store時(shí),我們提到過每個(gè)middleware都可以訪問store,即middlewareAPI這個(gè)變量,也可以拿到store的dispatch屬性。那么,在middleware中調(diào)用store.dispatch()會(huì)發(fā)生什么,和調(diào)用next()有什么區(qū)別?
const logger = store => next => action => {
console.log('dispatch:', action);
next(action);
console.log('finish:', action);
};
const logger = store => next => action => { c
onsole.log('dispatch:', action);
store.dispatch(action);
console.log('finish:', action);
};
在分發(fā) store 時(shí)我們解釋過,middleware 中 store 的 dispatch 通過匿名函數(shù)的方式和最終 compose 結(jié)束后的新 dispatch 保持一致,所以,在 middleware 中調(diào)用 store.dispatch() 和在其他 任何地方調(diào)用的效果一樣。而在 middleware 中調(diào)用 next(),效果是進(jìn)入下一個(gè) middleware。
這就是一個(gè)洋蔥模型
next代表下一個(gè)執(zhí)行的中間件,每次return回去的都是一個(gè)未執(zhí)行的函數(shù),只有最后調(diào)用才能執(zhí)行。
// 實(shí)現(xiàn)之后的效果
// 這時(shí)候返回的是一個(gè)經(jīng)過層層中間件封裝的dispatch,新的dispatch函數(shù)
newDispatch = M1(M2(M3(dispatch)))
// M1中的next 就是M2
// M2中的next就是M3
// M3中的next就是dispatch,執(zhí)行dispatch
// 調(diào)用
newDispatch(action)

正常情況下,如上圖左,我們分發(fā)一個(gè)action時(shí),middleware通過next(action)一層層傳遞和處理action直到Redux原生的dispathc。當(dāng)某個(gè)middleware使用store.dispatch(action)分發(fā)action,會(huì)發(fā)聲右圖的情況,就會(huì)形成無限循環(huán)。那么store.dispatch(action)的用武之地在哪里呢?
異步請(qǐng)求的時(shí)候,使用到Redux Thunk
const thunk = store => next => action => {
typeof action === 'function'?
action(store.dispatch, store.getState) : next(action)
}
Redux Thunk會(huì)判斷action是否是函數(shù)。如果是,執(zhí)行action,否則繼續(xù)傳遞action到下一個(gè)middleware。
const getThenShow = (dispatch, getState) => {
const url = 'http://xxx.json'
fetch(url)
.then((res) => {
dispatch({
type: 'SHOW_MESSAGE_FOR_ME',
message: res.json(),
})
}).catch( ()=> {
dispatch({
type: 'FETCH_DATA_FAIL',
message: 'error'
})
} )
}
// 再應(yīng)用中調(diào)用 store.dispatch(getThenShow)
Redux異步流
使用middleware簡(jiǎn)化異步請(qǐng)求
- redux-thunk
Thunk函數(shù)實(shí)現(xiàn)上就是針對(duì)多參數(shù)的currying以實(shí)現(xiàn)對(duì)函數(shù)的惰性求值。任何函數(shù),只要參數(shù)有回調(diào)函數(shù),就能寫成Thunk函數(shù)的形式。
redux-thunk的源代碼:
function createThunkMiddleware(extraArg) {
return ({dispath, getState} => next => action => {
if ( typeof action === 'function' ) {
return action(dispatch, getState, extraArg)
}
return next(action)
})
}
- redux-promise
抽象promise來解決異步流問題。
redux-promise 兼容了 FSA 標(biāo)準(zhǔn),也就是說將返回的結(jié)果保存在 payload 中。實(shí)現(xiàn)過程非常容易理解,即判斷 action 或action.payload是否為 promise,如果是,就執(zhí)行 then,返回的結(jié)果再發(fā)送一次 dispatch。
使用ES7的async和await語法,簡(jiǎn)化異步過程
const fetchData = (url, params) => fetch(url, params);
async function getWeather(url, params) {
const result = await fetchData(url, params);
if( result.error ) {
return {
type: 'GET_WEATHER_ERROR',
error: result.error
}
}
return {
type: 'GET_WEATHER_SUCCESS',
payload: result
}
}
- redux-composable-fetch
實(shí)際請(qǐng)求中,加上loading狀態(tài)
這時(shí)候異步請(qǐng)求的action
{
url: '/api/weather.json',
params: {
city: encodeURI(city),
},
types: ['GET_WEATHER', 'GET_WEATHER_SUCESS', 'GET_WEATHER_ERROR'],
}
和FSA不一樣了,沒有types,有了url和type代表請(qǐng)求狀態(tài)
const fetchMiddleware = store => next => action => {
if (!action.url || !Array.isArray(action.types)) {
return next(action);
}
const [LOADING, SUCCESS, ERROR] = action.types;
next({
type: LOADING,
loading: true,
...action,
});
fetch(action.url, { params: action.params })
.then(result => {
next({
type: SUCCESS,
loading: false,
payload: result,
});
})
.catch(err => {
next({
type: ERROR,
loading: false,
error: err,
});
});
}
使用middleware處理復(fù)雜異步流
1.輪詢
多異步串聯(lián)
使用Promiseredux-saga
最優(yōu)雅通用的解決方法,有靈活而強(qiáng)大的協(xié)程機(jī)制,可以解決任何復(fù)雜的異步交互。
Redux和路由
我們需要一個(gè)這樣的路由系統(tǒng),它既能利用React Router 的聲明式特性,又能將路由信息整合進(jìn) Redux store 中。
React Router
1.基本原理

-
React Router特性
React Router與React對(duì)比
- 聲明式的路由
// 實(shí)例
import { Router, Route, browserHistory } from 'react-router';
const routes = (
<Router history={browserHistory}>
<Route path='/' component{App} />
</Router>
)
- 嵌套路由及路徑匹配
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
const routes = (
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={MailList} />
<Route path="/mail/:mailId" component={Mail} />
</Route>
</Router>
);
App 組件承載了顯示頂欄和側(cè)邊欄的功能,而 React Router 會(huì)根據(jù)當(dāng)前的 url 自動(dòng)判斷該顯示郵件列表頁還是詳情頁:
. 當(dāng) url 為 / 時(shí),顯示列表頁;
. 當(dāng) url 為 /mail/123 時(shí),顯示詳情頁。
- 支持多種路由切換方式
hashChange 或是 history.pushState。hashChange 的方式擁有良好的瀏覽器兼容性,但是 url 中卻多了丑陋的 /#/ 部分;而 history.pushState 方法則能給我們提供優(yōu)雅的 url,卻需要額外的服務(wù)端配置解決任意路徑刷新的問題。
React Router Redux
當(dāng)我們采用 Redux 架構(gòu)時(shí),所有的應(yīng)用狀態(tài)必須放在一個(gè)單一的 store 中管理,路由狀態(tài)也不例外。而這就是 React Router Redux 為我們實(shí)現(xiàn)的主要功能。
- React Router與Redux store綁定
React Router Redux 提供了簡(jiǎn)單直白的 API——syncHistoryWithStore 來完成與 Redux store的綁定工作。
import { browserHistory } from 'react-router'
import { syncHistoryWithStore } from 'react-router-redux'
import reducers from '<project-path>/reducers'
const store = createStore(reducers);
const history = syncHistoryWithStore(browserHistory, store);
- 用Redux的方式改變路由
對(duì)Redux的store進(jìn)行增強(qiáng),以便分發(fā)的action能被正確識(shí)別
import { browserHistory } from 'react-router';
import { routerMiddleware } from 'react-router-redux';
const middleware = routerMiddleware(browserHistory);
const store = createStore(
reducers,
applyMiddleware(middleware)
);
使用
import { push } from 'react-router-redux';
// 切換路由到 /home
store.dispatch(push('/home'));
Redux 與 組件

Redux中,強(qiáng)調(diào)了3中不同類型的布局組件:Layouts、Views和Components。它常常是無狀態(tài)函數(shù),傳入主體內(nèi)容的children屬性。
const Layout = ({ children } => {
<div className = 'container'>
<Header />
<div className="contaier">
{ children }
</div>
</div>
})
- Views
子路由入口組件,描述子路由入口的基本結(jié)構(gòu),包含此路由下所有的展示型組件。
@connect((state) => {
//...
})
class HomeView extends Component {
render() {
const { sth, changeType } = this.props;
const cardProps = { sth, changeType };
return (
<div className="page page-home">
<Card {...cardProps} />
</div>
);
}
}
3、Components
末級(jí)渲染組件,描述了從路由以下的子組件。包含具體的業(yè)務(wù)邏輯和交互,但所有的數(shù)據(jù)和action都是油Views傳下來。
class Card extends Components {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(opts) {
const { type } = opts;
this.props.changeType(type);
}
render() {
const { sth } = this.props;
return (
<div className="mod-card">
<Switch onChange={this.handleChange}>
// ...
</Switch>
{sth}
</div>
);
}
}
