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

本文不涉及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的工作流程有更深刻的理解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容