从一个nba应用理解react-redux工作原理

本文不涉及react-native和es6的相关语法,只描述react-redux部分的工作原理。在描述例子之前,先捋清楚各部分的概念。

1.Redux的工作原理


先上一张图


这就是redux的工作流程,接下来将描述图中的各个概念。

Store与State


在Redux中,整个应用被看作一个状态机(State),所有状态保存在一个对象中(Store)。
整个应用只有一个Store对象,它是一个保存数据的容器,而State对象包含在某一状态下保存的数据,当前状态的State可以通过下面的方式拿到

store.getState()

在这里每个State对应一个View,State变化View也跟着变化,但是用户只能接触到View,不能直接改变State,它们之间的通讯是通过Action来完成的

Action


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
  }
}

Reducer


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 的自动渲染。

整理过程


再回头看工作流程图,就应该能明晰整个过程
1)用户通过View发出Action,即store.dispatch(action)
2)Store接受到Action,自动调用Reducer处理Action
3)Reducer根据当前State和传入的Action生成新State
4)State变化之后Store调用设置好的监听函数,监听函数通过setState
为新的State,实现 View 的自动渲染。

2. react-thunk中间件


上文中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中间件,完成异步操作了。

3.nba应用和react-redux的工作原理


在描述react-redux之前,先看看项目的结构


我们看到了熟悉的actions,reducers,middleware,而lib和utils是一些工具和自定义组件。components,containers和channel才是接下来要描述的地方。在react-redux中将组件分为两个类型

presentational component


用于表现ui的组件,不带任何业务逻辑也没有状态。

container component


负责业务逻辑带有状态,不负责ui呈现

connect方法


从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。

nba应用代码分析


理解了上面的概念,接下来就可以借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 (
      
        {() => }
      
    )
  }
}

一切从创建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 (
      
        {tab === 'game' &&
          
        }
        {tab === 'players' &&
          
        }
        {tab === 'teams' &&
          
        }
      
    )
  }
}

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


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的工作流程有更深刻的理解

你可能感兴趣的:(从一个nba应用理解react-redux工作原理)