從一個(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)系作者
  • 序言:七十年代末弊添,一起剝皮案震驚了整個(gè)濱河市录淡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌油坝,老刑警劉巖嫉戚,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異免钻,居然都是意外死亡彼水,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門极舔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凤覆,“玉大人,你說我怎么就攤上這事拆魏《㈣耄” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵渤刃,是天一觀的道長(zhǎng)拥峦。 經(jīng)常有香客問我,道長(zhǎng)卖子,這世上最難降的妖魔是什么略号? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上玄柠,老公的妹妹穿的比我還像新娘突梦。我一直安慰自己,他們只是感情好羽利,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布宫患。 她就那樣靜靜地躺著,像睡著了一般这弧。 火紅的嫁衣襯著肌膚如雪娃闲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天匾浪,我揣著相機(jī)與錄音皇帮,去河邊找鬼。 笑死户矢,一個(gè)胖子當(dāng)著我的面吹牛玲献,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播梯浪,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼捌年,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了挂洛?” 一聲冷哼從身側(cè)響起礼预,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎虏劲,沒想到半個(gè)月后托酸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡柒巫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年励堡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片堡掏。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡应结,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泉唁,到底是詐尸還是另有隱情鹅龄,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布亭畜,位于F島的核電站扮休,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏拴鸵。R本人自食惡果不足惜玷坠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一蜗搔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸轮傍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厉膀。三九已至溶耘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間服鹅,已是汗流浹背凳兵。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留企软,地道東北人庐扫。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像仗哨,于是被迫代替她去往敵國和親形庭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容