本文不涉及react-native和es6的相关语法,只描述react-redux部分的工作原理。在描述例子之前,先捋清楚各部分的概念。
<h1>1.Redux的工作原理</h1>
先上一张图
这就是redux的工作流程,接下来将描述图中的各个概念。
<h2>Store与State</h2>
在Redux中,整个应用被看作一个状态机(State),所有状态保存在一个对象中(Store)。
整个应用只有一个Store对象,它是一个保存数据的容器,而State对象包含在某一状态下保存的数据,当前状态的State可以通过下面的方式拿到
store.getState()
在这里每个State对应一个View,State变化View也跟着变化,但是用户只能接触到View,不能直接改变State,它们之间的通讯是通过Action来完成的
<h2>Action</h2>
Action是一个对象,是View向State发出的信息。这个对象包括一个必须的type属性,和其它自定义的属性。形如
const action={
type:'GETSOMETHING',
data
};
这个Action对象包含值为'GETSOMETHING'的type属性和一个自定义data属性。
想要改变State,就要使用Action,Action会携带信息传递到Store。Action可以通过如下方式发送
store.dispatch(action)
图中的Action Creator是一个用于生成Action对象的函数,形如
addAction=(data)=>{
return{
type:'GETSOMETHING',
data
}
}
<h2>Reducer</h2>
Action被传递到Store后,Store需要根据Action生成新的State,这一工作通过Reducer完成。
Reducer是一个接受当前State和Action为参数返回新的State的函数,形如
const initialState = {};
const reducer=(initialState,action)=>{
switch(action.type)
case 'GETSOMETHING':
return Object.assign({}, initialState, {
data: action.data,
});
default:
return initialState;
}
上文的store.dispatch(action)方法会触发 Reducer 的自动执行。为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入createStore方法。
const store=createStore(reducer)
createStore接受 Reducer 作为参数,生成一个新的 Store。以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。更新State之后就需要更新View,Store允许通过store.subscribe方法设置监听函数,将setState放入监听函数中,就会实现 View 的自动渲染。
<h2>整理过程</h2>
再回头看工作流程图,就应该能明晰整个过程
1)用户通过View发出Action,即store.dispatch(action)
2)Store接受到Action,自动调用Reducer处理Action
3)Reducer根据当前State和传入的Action生成新State
4)State变化之后Store调用设置好的监听函数,监听函数通过setState
为新的State,实现 View 的自动渲染。
<h1>2. react-thunk中间件</h1>
上文中Action发出后,Reducer直接返回新的State,这里是一个同步的工程,但是正常应用中不可能所有操作都是同步的,如果需要异步操作该怎么办呢?
这就需要我们在操作结束时发送一个Action表示操作结束。如下
const getGameGeneral=(year,month,date)=>{
return(dispatch,getState)=>{
return getGameGeneral(year,month,date)
.then(data=>{
dispatch({
type:'GAMEGEN',
data
});
})
}
};
上面代码中的getGameGeneral是一个Action Creator,但也redux规定的Action Creator不同:1.getGameGeneral返回一个函数而不是Action对象,2.getGameGeneral接受dispatch和getState两个方法作为参数而不是Action的内容。
这时,就要使用react-thunk中间件,改造store.dispatch,使得后者可以接受函数作为参数。
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
const createStoreWithMW = applyMiddleware(thunk)(createStore)
const store = createStoreWithMW(reducers)
这样就能使用react-thunk中间件,完成异步操作了。
<h1>3.nba应用和react-redux的工作原理</h1>
在描述react-redux之前,先看看项目的结构
我们看到了熟悉的actions,reducers,middleware,而lib和utils是一些工具和自定义组件。components,containers和channel才是接下来要描述的地方。在react-redux中将组件分为两个类型
<h2>presentational component</h2>
用于表现ui的组件,不带任何业务逻辑也没有状态。
<h2>container component</h2>
负责业务逻辑带有状态,不负责ui呈现
<h2>connect方法</h2>
从presentational component生成container component需要使用connect方法,使用方法如下
const containerComponent=connect(
mapStateToProps,
mapDispatchToProps
)(presentationalComponent)
其中 mapStateToProps是一个将state对象映射到组件props的函数,比如
state => {
return {
application: state.application
}
它接受state作为参数,返回一个对象,把state对象的相关值映射到组件作为组件的props
而 mapDispatchToProps是一个将store.dispatch映射到组件props的函数,它决定用户的什么行为会被当做什么Action被发送,比如
dispatch => {
return {
gameActions: () => {
dispatch({
type: 'GAME',
data
});
}
}
mapDispatchToProps应该返回一个对象,该对象的每个键值对都是一个映射,定义了 UI 组件的参数怎样发出 Action。
<h2>nba应用代码分析</h2>
理解了上面的概念,接下来就可以借nba应用中获取球员列表的逻辑功能来分析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>
)
}
}
一切从创建store开始,使用react-thunk中间件,其中的Provider组件由react-redux提供,可以让容器组件拿到state。Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到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)
我们可以看到这里先创建了一个名为App的presentational component 然后使用connect将state和dispatch映射到这个组件上。
与获取球员列表相关的是
player: {
playerList: state.playerList,
playerLoaded: state.playerLoaded
},
将state中的playerList和playerLoaded两个属性作为对象player的两个值映射到App组件中
playerActions: bindActionCreators(Object.assign({}, applicationActions, playerActions), dispatch),
bindActionCreators是把多个action用dispatch调用,这里在下面看Action的代码时会描述,这里就是mapDispatchToProps
<Player {...player} actions={playerActions} />
connect方法调用后,App组件有了名为player和playerActions的两个属性,将它们传递给Player组件
再看Player组件中的代码
componentDidMount () {
const {actions} = this.props
actions.getPlayerList()
.then(() => {
actions.getSearchRecord()
})
}
componentWillReceiveProps (props) {
const {playerList} = props
this.playerList = playerList.data
}
组件Mount后(componentDidMount)通过从props获取改发送的Aciton,发送action,获取新的state,其中包含获取的playerList在App组件中映射给该组件。当异步操作结束后(componentWillReceiveProps)更新playerList的数据,渲染View。
而上述过程中的发送action后计算新的state与原本的redux过程没有区别,这里贴一下playeraction和相应的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的作用是将一个或多个action和dispatch组合起来,这里看到这段代码并没有像mapDispatchToProps的值的一样手动通过dispatch传递一个action,这个过程是通过bindActionCreators来完成的,这样就不需要在每个action中都手动dispatch
const initialState = {
isLoaded: false,
recent: [],
data: []
}
const actionHandler = {
[PLAYER.LIST]: (state, action) => {
return {
isLoaded: true,
data: action.data
}
}
}
export default createReducer(initialState, actionHandler)
这就是一个使用了react-redux的react-native应用的实例描述。
最后再上一张图
看着这张图回想刚才的过程,应该可以对react-redux的工作流程有更深刻的理解