從一個(gè)nba應(yīng)用理解react-redux工作原理

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容