本文不涉及react-native和es6的相關(guān)語法,只描述react-redux部分的工作原理。在描述例子之前,先捋清楚各部分的概念佩研。
<h1>1.Redux的工作原理</h1>
先上一張圖
這就是redux的工作流程,接下來將描述圖中的各個(gè)概念霞揉。
<h2>Store與State</h2>
在Redux中旬薯,整個(gè)應(yīng)用被看作一個(gè)狀態(tài)機(jī)(State),所有狀態(tài)保存在一個(gè)對(duì)象中(Store)适秩。
整個(gè)應(yīng)用只有一個(gè)Store對(duì)象绊序,它是一個(gè)保存數(shù)據(jù)的容器硕舆,而State對(duì)象包含在某一狀態(tài)下保存的數(shù)據(jù),當(dāng)前狀態(tài)的State可以通過下面的方式拿到
store.getState()
在這里每個(gè)State對(duì)應(yīng)一個(gè)View,State變化View也跟著變化骤公,但是用戶只能接觸到View抚官,不能直接改變State,它們之間的通訊是通過Action來完成的
<h2>Action</h2>
Action是一個(gè)對(duì)象阶捆,是View向State發(fā)出的信息凌节。這個(gè)對(duì)象包括一個(gè)必須的type屬性,和其它自定義的屬性洒试。形如
const action={
type:'GETSOMETHING',
data
};
這個(gè)Action對(duì)象包含值為'GETSOMETHING'的type屬性和一個(gè)自定義data屬性倍奢。
想要改變State,就要使用Action垒棋,Action會(huì)攜帶信息傳遞到Store卒煞。Action可以通過如下方式發(fā)送
store.dispatch(action)
圖中的Action Creator是一個(gè)用于生成Action對(duì)象的函數(shù),形如
addAction=(data)=>{
return{
type:'GETSOMETHING',
data
}
}
<h2>Reducer</h2>
Action被傳遞到Store后叼架,Store需要根據(jù)Action生成新的State跷坝,這一工作通過Reducer完成。
Reducer是一個(gè)接受當(dāng)前State和Action為參數(shù)返回新的State的函數(shù),形如
const initialState = {};
const reducer=(initialState,action)=>{
switch(action.type)
case 'GETSOMETHING':
return Object.assign({}, initialState, {
data: action.data,
});
default:
return initialState;
}
上文的store.dispatch(action)方法會(huì)觸發(fā) Reducer 的自動(dòng)執(zhí)行碉碉。為此柴钻,Store 需要知道 Reducer 函數(shù),做法就是在生成 Store 的時(shí)候垢粮,將 Reducer 傳入createStore方法贴届。
const store=createStore(reducer)
createStore接受 Reducer 作為參數(shù),生成一個(gè)新的 Store蜡吧。以后每當(dāng)store.dispatch發(fā)送過來一個(gè)新的 Action毫蚓,就會(huì)自動(dòng)調(diào)用 Reducer,得到新的 State昔善。更新State之后就需要更新View元潘,Store允許通過store.subscribe方法設(shè)置監(jiān)聽函數(shù),將setState放入監(jiān)聽函數(shù)中君仆,就會(huì)實(shí)現(xiàn) View 的自動(dòng)渲染翩概。
<h2>整理過程</h2>
再回頭看工作流程圖,就應(yīng)該能明晰整個(gè)過程
1)用戶通過View發(fā)出Action返咱,即store.dispatch(action)
2)Store接受到Action钥庇,自動(dòng)調(diào)用Reducer處理Action
3)Reducer根據(jù)當(dāng)前State和傳入的Action生成新State
4)State變化之后Store調(diào)用設(shè)置好的監(jiān)聽函數(shù),監(jiān)聽函數(shù)通過setState
為新的State咖摹,實(shí)現(xiàn) View 的自動(dòng)渲染评姨。
<h1>2. react-thunk中間件</h1>
上文中Action發(fā)出后,Reducer直接返回新的State萤晴,這里是一個(gè)同步的工程吐句,但是正常應(yīng)用中不可能所有操作都是同步的胁后,如果需要異步操作該怎么辦呢?
這就需要我們?cè)诓僮鹘Y(jié)束時(shí)發(fā)送一個(gè)Action表示操作結(jié)束嗦枢。如下
const getGameGeneral=(year,month,date)=>{
return(dispatch,getState)=>{
return getGameGeneral(year,month,date)
.then(data=>{
dispatch({
type:'GAMEGEN',
data
});
})
}
};
上面代碼中的getGameGeneral是一個(gè)Action Creator攀芯,但也redux規(guī)定的Action Creator不同:1.getGameGeneral返回一個(gè)函數(shù)而不是Action對(duì)象,2.getGameGeneral接受dispatch和getState兩個(gè)方法作為參數(shù)而不是Action的內(nèi)容净宵。
這時(shí)敲才,就要使用react-thunk中間件,改造store.dispatch择葡,使得后者可以接受函數(shù)作為參數(shù)紧武。
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
const createStoreWithMW = applyMiddleware(thunk)(createStore)
const store = createStoreWithMW(reducers)
這樣就能使用react-thunk中間件,完成異步操作了敏储。
<h1>3.nba應(yīng)用和react-redux的工作原理</h1>
在描述react-redux之前阻星,先看看項(xiàng)目的結(jié)構(gòu)
我們看到了熟悉的actions,reducers已添,middleware妥箕,而lib和utils是一些工具和自定義組件。components更舞,containers和channel才是接下來要描述的地方畦幢。在react-redux中將組件分為兩個(gè)類型
<h2>presentational component</h2>
用于表現(xiàn)ui的組件,不帶任何業(yè)務(wù)邏輯也沒有狀態(tài)缆蝉。
<h2>container component</h2>
負(fù)責(zé)業(yè)務(wù)邏輯帶有狀態(tài)宇葱,不負(fù)責(zé)ui呈現(xiàn)
<h2>connect方法</h2>
從presentational component生成container component需要使用connect方法,使用方法如下
const containerComponent=connect(
mapStateToProps,
mapDispatchToProps
)(presentationalComponent)
其中 mapStateToProps是一個(gè)將state對(duì)象映射到組件props的函數(shù)刊头,比如
state => {
return {
application: state.application
}
它接受state作為參數(shù)黍瞧,返回一個(gè)對(duì)象,把state對(duì)象的相關(guān)值映射到組件作為組件的props
而 mapDispatchToProps是一個(gè)將store.dispatch映射到組件props的函數(shù)原杂,它決定用戶的什么行為會(huì)被當(dāng)做什么Action被發(fā)送印颤,比如
dispatch => {
return {
gameActions: () => {
dispatch({
type: 'GAME',
data
});
}
}
mapDispatchToProps應(yīng)該返回一個(gè)對(duì)象,該對(duì)象的每個(gè)鍵值對(duì)都是一個(gè)映射穿肄,定義了 UI 組件的參數(shù)怎樣發(fā)出 Action年局。
<h2>nba應(yīng)用代碼分析</h2>
理解了上面的概念,接下來就可以借nba應(yīng)用中獲取球員列表的邏輯功能來分析react-redux的工作原理了
先看root.js
'use strict'
import React, {
Component,
StatusBarIOS,
Platform
} from 'react-native'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux/native'
import reducers from './reducers'
import thunk from 'redux-thunk'
import App,{APP} from './containers/App'
const createStoreWithMW = applyMiddleware(thunk)(createStore)
const store = createStoreWithMW(reducers)
export default class Root extends Component {
componentDidMount () {
if (Platform.OS === 'ios') {
StatusBarIOS.setHidden(true)
}
}
render () {
return (
<Provider store={store}>
{() => <App />}
</Provider>
)
}
}
一切從創(chuàng)建store開始被碗,使用react-thunk中間件某宪,其中的Provider組件由react-redux提供,可以讓容器組件拿到state锐朴。Provider在根組件外面包了一層,這樣一來蔼囊,App的所有子組件就默認(rèn)都可以拿到state了焚志。
然后是App.js的部分代碼
export class App extends Component {
constructor (props) {
super(props)
this.state = {
tab: null
}
}
componentWillReceiveProps (props) {
const {application} = props
this.setState({
tab: application.tab
})
}
render () {
const {tab} = this.state
const {game, player, team, gameActions, playerActions, teamActions} = this.props
return (
<View style={styles.container}>
{tab === 'game' &&
<Game {...game} actions={gameActions} />
}
{tab === 'players' &&
<Player {...player} actions={playerActions} />
}
{tab === 'teams' &&
<Team {...team} actions={teamActions} />
}
</View>
)
}
}
export default connect(state => {
return {
application: state.application,
game: {
live: state.live,
over: state.over,
unstart: state.unstart,
standing: state.standing,
application: state.application
},
player: {
playerList: state.playerList,
playerLoaded: state.playerLoaded
},
team: {
team: state.team,
playerLoaded: state.playerLoaded
}
}
}, dispatch => {
return {
gameActions: bindActionCreators(Object.assign({}, applicationActions, gameActions), dispatch),
playerActions: bindActionCreators(Object.assign({}, applicationActions, playerActions), dispatch),
teamActions: bindActionCreators(Object.assign({}, applicationActions, playerActions, teamActions), dispatch)
}
})(App)
我們可以看到這里先創(chuàng)建了一個(gè)名為App的presentational component 然后使用connect將state和dispatch映射到這個(gè)組件上衣迷。
與獲取球員列表相關(guān)的是
player: {
playerList: state.playerList,
playerLoaded: state.playerLoaded
},
將state中的playerList和playerLoaded兩個(gè)屬性作為對(duì)象player的兩個(gè)值映射到App組件中
playerActions: bindActionCreators(Object.assign({}, applicationActions, playerActions), dispatch),
bindActionCreators是把多個(gè)action用dispatch調(diào)用,這里在下面看Action的代碼時(shí)會(huì)描述酱酬,這里就是mapDispatchToProps
<Player {...player} actions={playerActions} />
connect方法調(diào)用后壶谒,App組件有了名為player和playerActions的兩個(gè)屬性,將它們傳遞給Player組件
再看Player組件中的代碼
componentDidMount () {
const {actions} = this.props
actions.getPlayerList()
.then(() => {
actions.getSearchRecord()
})
}
componentWillReceiveProps (props) {
const {playerList} = props
this.playerList = playerList.data
}
組件Mount后(componentDidMount)通過從props獲取改發(fā)送的Aciton膳沽,發(fā)送action汗菜,獲取新的state,其中包含獲取的playerList在App組件中映射給該組件挑社。當(dāng)異步操作結(jié)束后(componentWillReceiveProps)更新playerList的數(shù)據(jù)陨界,渲染View。
而上述過程中的發(fā)送action后計(jì)算新的state與原本的redux過程沒有區(qū)別痛阻,這里貼一下playeraction和相應(yīng)的reducer的代碼
const getPlayerList=()=>{
return (dispatch, getStore) => {
if (getStore().playerReducer.isLoaded) {
return Promise.resolve(dispatch({
type: 'PLAYERLST',
data: getStore().playerReducer.data
}))
}
const channel =new Channel();
return channel.getPlayerList('2016-17')
.then(data=>{
dispatch({
type:'PLAYERLST',
data
});
})
.catch(err => console.error(err))
}
};
前文中的bindActionCreators的作用是將一個(gè)或多個(gè)action和dispatch組合起來菌瘪,這里看到這段代碼并沒有像mapDispatchToProps的值的一樣手動(dòng)通過dispatch傳遞一個(gè)action,這個(gè)過程是通過bindActionCreators來完成的阱当,這樣就不需要在每個(gè)action中都手動(dòng)dispatch
const initialState = {
isLoaded: false,
recent: [],
data: []
}
const actionHandler = {
[PLAYER.LIST]: (state, action) => {
return {
isLoaded: true,
data: action.data
}
}
}
export default createReducer(initialState, actionHandler)
這就是一個(gè)使用了react-redux的react-native應(yīng)用的實(shí)例描述俏扩。
最后再上一張圖
看著這張圖回想剛才的過程,應(yīng)該可以對(duì)react-redux的工作流程有更深刻的理解