Redux初步理解

Redux筆記

參考理解

Redux 中文文檔
Redux 阮一峰

嚴格的單向數(shù)據(jù)流是Rduex設計核心。

Redux簡單概括:單一的state是存儲在store中再扭,當要對state進行更新的時候泛范,首先要發(fā)起一個action(通過dispatch

函數(shù))敦跌,action的作用就相當于一個消息通知柠傍,用來描述發(fā)生了什么(比如:增加一個TODO)惧笛,然后reducer會根據(jù)action來
進行對state更新患整,這樣就可以更新新的state去渲染View.

從不直接修改 state 是 Redux 的核心理念之一, 所以你會發(fā)現(xiàn)自己總是在使用 Object.assign() 創(chuàng)建對象拷貝, 而拷貝中會包含新創(chuàng)建或更新過的屬性值各谚。在下面的 todoApp 示例中, Object.assign() 將會返回一個新的 state 對象, 而其中的 visibilityFilter 屬性被更新了:

function todoApp(state = initialState, action) {
    switch (action.type) {
      case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
      visibilityFilter: action.filter
     })
   default:
    return state
  }
}

Redux適用場景

  1. 用戶的使用方式復雜(Ul界面復雜昌渤,操作內(nèi)容多)
  2. 不同身份的用戶有不同的使用方式(普通用戶和管理員)
  3. 多個用戶之間可以協(xié)作
  4. 與服務器有大量交互
  5. View要從多個來源獲取數(shù)據(jù)

從組件看膀息,存在以下場景可以考慮使用Redux

  1. 某個組件的狀態(tài)需要共享
  2. 某個狀態(tài)需要在任何地方可以拿到
  3. 一個組件需要改變?nèi)譅顟B(tài)
  4. 一個組件需要改變另一個組件的狀態(tài)

要點

  1. 應用中所有的state都以一個對象樹的形式存儲在一個單一的store中
  1. 唯一改變state的辦法就是觸發(fā)action,一個描述發(fā)生什么的對象
  2. 為了描述action如何改變state樹潜支,需要編寫reducers.

store

store 充當一個容器冗酿,用來保存數(shù)據(jù)的地方鸠窗。也可以理解成存儲state的地方胯究。整個應用 只能 有一個Store
由于整個應用只有一個store裕循,所以store保存了所有的數(shù)據(jù)≈暧ぃ可以通過store.getState()獲取當前時刻的state.

store 里能直接通過 store.dispatch() 調(diào)用 dispatch() 方法暑认,但是多數(shù)情況下你會使用 react-redux 提供的 connect() 幫助器來調(diào)用蘸际。

action

Action 本質(zhì)上是 JavaScript 普通對象粮彤。我們約定导坟,action 內(nèi)必須使用一個字符串類型的 type 字段來表示將要執(zhí)行的動作。多數(shù)情況下尘惧,type 會被定義成字符串常量褥伴。當應用規(guī)模越來越大時重慢,建議使用單獨的模塊或文件來存放 action似踱。
action是由用戶操作view產(chǎn)生的核芽。view的改變產(chǎn)生action,action的改變會傳到store驰坊,進而影響state的改變拳芙。

import { createStore } from 'redux';
const store = createStore(fn);
store.dispatch({
  type: 'ADD_TODO',
  payload: 'Learn Redux'
});
通過store.dispatch()將action傳遞到store里面舟扎。

reducer

store 接收到action后睹限,必須給出一個新的state,從而是view做出變化羡疗。這樣的一個計算過程叫做Reducer.
Reducer是一個 函數(shù)顺囊,接收action和當前的state作為參數(shù),然后返回一個新的state.

const reducer = function (state, action) {
  // ...
  return new_state;};

因為Reducer函數(shù)負責生成state午乓,而整個應用只有一個state,所以當state非常大的時候益愈,導致Reduce函數(shù)也非常的大蒸其,
根據(jù)action不同的種類摸袁,我們可以將reducer拆分為多個小的reducer义屏,最后再合成一個大的reducer靠汁。

const chatReducer = (state = defaultState, action = {}) => {
const { type, payload } = action;
switch (type) {
case ADD_CHAT:
  return Object.assign({}, state, {
    chatLog: state.chatLog.concat(payload)
  });
case CHANGE_STATUS:
  return Object.assign({}, state, {
    statusMessage: payload
  });
case CHANGE_USERNAME:
  return Object.assign({}, state, {
    userName: payload
  });
default: return state;
}};
上面這個Reducer包含了3個action,顯得比較大蜂大,比較臃腫。
我們可以對其進行拆分函數(shù):
const chatReducer = (state = defaultState, action = {}) => {
return {
chatLog: chatLog(state.chatLog, action),
statusMessage: statusMessage(state.statusMessage, action),
userName: userName(state.userName, action)
}};
這樣一個大的Reducer函數(shù)就拆分成三個小函數(shù)蝶怔,每個小函數(shù)負責生成對應屬性奶浦。這樣就可以把小的函數(shù)理解成子reducer函數(shù)。
這就與react的根組件與子組件的概念吻合踢星,根組件對應最終生成的大的Reducer澳叉,而子組件對應每個子reducer。

拆開成子reducer
function todos(state = [], action) {
  switch (action.type) {
case ADD_TODO:
  return [
    ...state,
    {
      text: action.text,
      completed: false
    }
  ]
case TOGGLE_TODO:
  return state.map((todo, index) => {
    if (index === action.index) {
      return Object.assign({}, todo, {
        completed: !todo.completed
      })
    }
    return todo
  })
default:
  return state
 }
  }
 function visibilityFilter(state = SHOW_ALL, action) {
   switch (action.type) {
case SET_VISIBILITY_FILTER:
  return action.filter
default:
  return state
  }
  }
  function todoApp(state = {}, action) {
    return {
   visibilityFilter: visibilityFilter(state.visibilityFilter, action),
   todos: todos(state.todos, action)
}}

通過使用redux提供的combinReducers()來實現(xiàn)上面todoApp做的事情
import { combineReducers } from 'redux';
  const todoApp = combineReducers({
  visibilityFilter,
  todos
 })
 上面的代碼等價于下面的代碼
 export default function todoApp(state = {}, action) {
   return {
     visibilityFilter: visibilityFilter(state.visibilityFilter, action),
     todos: todos(state.todos, action)
  }}

export default todoApp;

Redux提供combineReducers方法,用于將定義的各個子Reducer函數(shù)合并成一個大的Reducer.

import { combineReducers } from 'redux';
const chatReducer = combineReducers({
  chatLog,
  statusMessage,
  userName
})

理解

import { createStore } from 'redux';
const store = createStore(reducer);
store.subscribe(listener); 

通過 reducer 創(chuàng)建一個 store 斩狱,每當我們在 store 上 dispatch 一個 action 秕岛,store 內(nèi)的數(shù)據(jù)就會相應地發(fā)生變化。

Store提供了三種方法:store.getState(),store.dispatch(),store.subscribe().
getState()用來獲取store里面當前的state。
dispatch()用來傳遞action到store里面,可以在任何地方調(diào)用 store.dispatch(action),包括組件中诱贿、XHR 回調(diào)中焙蹭、甚至定時器中烟馅。
subscribe()用來監(jiān)聽事件,接受的參數(shù)應當是個函數(shù),該函數(shù)應當是在這里獲得store的最新state然后用來改變組件state的。

正常的思路應該是view發(fā)出action通過dispatch()傳遞給reducer進行相關(guān)的計算從而得出新的state。觀察上面創(chuàng)建store的代碼击儡,可以發(fā)現(xiàn)我們是
先把reducer這個計算函數(shù)“放入”store里面,所以我們就可以實現(xiàn)當我們把action通過dispatch()傳遞給store后,不需要自己手動去調(diào)用reducer,
store會自己自動調(diào)用。

怎么使用subscribe()訂閱來更新ui弄企,connect如何使用

使用方法

明智的做法是只在最頂層組件

使用React-redux

首先在最外層容器中约素,把所有內(nèi)容包裹在 Provider 組件中送悔,將之前創(chuàng)建的 store作為 prop 傳給 Provider 洁段。

參考理解

const App = () => {
  return (
    <Provider store={store}>
      <Comp/>
    </Provider>
  )
};

Provider 內(nèi)的任何一個組件(比如這里的 Comp ),如果需要使用 state 中的數(shù)據(jù)龙考,就必須是「被 connect 過的」組件——使用 connect 方法對「你編寫的組件( MyComp )」進行包裝后的產(chǎn)物。

class MyComp extends Component {
     // content...
}

const Comp = connect(...args)(MyComp);

connect的使用方法 參考理解

connect([mapStateToProps], [mapDispatchToProps], [mergeProps],[options])

connect的作用:連接React組件與Redux store會自己自動調(diào)用坛怪。
connect的第一個參數(shù)是mapStateToProps函數(shù)内狗, 該函數(shù)允許我們將我們將store中的數(shù)據(jù)作為props綁定到組件上偎行。

mapStateToProps(state, ownProps) : stateProps。
const mapStateToProps = (state) => {
   return {
      count: state.count
    }
}
  1. mapStateToProps函數(shù)的第一個參數(shù)就是Redux的store(整個state)從上面的示例我們可以看出我們沒有將store中所有的數(shù)據(jù)(state)全部
    傳入connect的組件矗漾,我們可以根據(jù)所要連接組件所需的props,從store中的state動態(tài)的輸出該組件需要的props.
  2. mapStateToProps函數(shù)的第二個參數(shù)ownProps,是連接組件的props.

使用ownProps示例:(比如點擊人員列表查看人員詳細信息巩梢,點擊事件傳遞組件(該組件只維護一個用戶信息)人員Id屬性props,然后可以通過這個組件自己的props去store獲取對應數(shù)據(jù))

const mapStateToProps = (state, ownProps) => {
  // state 是 {userList: [{id: 0, name: '王二'}]}
    return {
        user: _.find(state.userList, {id: ownProps.userId})
     }
   }
class MyComp extends Component {
 static PropTypes = {
    userId: PropTypes.string.isRequired,
    user: PropTypes.object
  };
render(){
    return <div>用戶名:{this.props.user.name}</div>
    }
}

const Comp = connect(mapStateToProps)(MyComp);

當Redux中的store(state)變化或者ownProps變化的時候感混,mapStateToProps都會被調(diào)用犀忱,計算出一個新的stateProps,(再與ownProps merge后)更新給組件.如果省略了這個參數(shù)哥倔,你的組件將不會監(jiān)聽 Redux store.

mapDispatchToProps(dispatch, ownProps): dispatchProps

connect 的第二個參數(shù)是 mapDispatchToProps芽突,它的功能是艘刚,將 action 作為 props 綁定到組件上.如果省略這個 mapDispatchToProps 參數(shù)荚斯,默認情況下,dispatch 會注入到你的組件 props 中胡岔。該函數(shù)如果傳遞的是一個對象,那么每個定義在該對象的函數(shù)都將被當作 Redux action creator,而且這個對象會與 Redux store 綁定在一起,其中所定義的方法名將作為屬性名,合并到組件的 props 中与斤。如果傳遞的是一個函數(shù),該函數(shù)將接收一個 dispatch 函數(shù),然后由你來決定如何返回一個對象锭弊,這個對象通過 dispatch 函數(shù)與 action creator 以某種方式綁定在一起

不管是 stateProps 還是 dispatchProps爽醋,都需要和 ownProps merge 之后才會被賦給組件。connect 的第三個參數(shù)就是用來做這件事。通常情況下惋啃,你可以不傳這個參數(shù)哼鬓,connect 就會使用 Object.assign 替代該方法

代碼示例

import React, {Component} from 'react'
class Counter extends Component {
    render() {
        //從組件的props屬性中導入四個方法和一個變量
        const {increment, decrement, counter} = this.props;
        //渲染組件,包括一個數(shù)字边灭,四個按鈕
        return (
            <p>
                Clicked: {counter} times
                {' '}
                <button onClick={increment}>+</button>
                {' '}
                <button onClick={decrement}>-</button>
                {' '}
            </p>
        )
    }
}
export default Counter;

import { connect } from 'react-redux'
import Counter from '../components/Counter'
import actions from '../actions/counter';
//將state.counter綁定到props的counter. 哪些 Redux 全局的 state 是我們組件想要通過 props 獲取的异希?
const mapStateToProps = (state) => {
    return {
        counter: state.counter
    }
};
//將action的所有方法綁定到props上.哪些 action 創(chuàng)建函數(shù)是我們想要通過 props 獲取的?
const mapDispatchToProps = (dispatch, ownProps) => {
    return {
        increment: (...args) => dispatch(actions.increment(...args)),
        decrement: (...args) => dispatch(actions.decrement(...args))
    }
};
//通過react-redux提供的connect方法將我們需要的state中的數(shù)據(jù)和actions中的方法綁定到props上
export default connect(mapStateToProps, mapDispatchToProps)(Counter)

Middleware

作用的位置:位于action被發(fā)起之后绒瘦,到達reducer之前的擴展點

Redux moddleware provides a third-party extension point between dispatching an action, and the moment it reaches the reducer

理解:在action和reducer中間插入middleware称簿,通過改變數(shù)據(jù)流扣癣,實現(xiàn)異步action.

可以干什么:進行日志記錄、創(chuàng)建崩潰報告憨降、調(diào)用異步接口或者路由

怎么使用Middleware:redux 提供了applyMiddleware這個api來加載middleware

項目中使用Middleware的方式:

import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import createLogger from 'redux-logger'
import rootReducer from './reducers'

const loggerMiddleware = createLogger()

const createStoreWithMiddleware = applyMiddleware(
  thunkMiddleware,
  loggerMiddleware
)(createStore)

export default function configureStore(initialState) {
  return createStoreWithMiddleware(rootReducer, initialState)
}

applyMiddleware源碼:

export default function applyMiddleware(...middlewares) {            return (next)  => 
        (reducer, initialState) => {

              var store = next(reducer, initialState);
              var dispatch = store.dispatch;
              var chain = [];

              var middlewareAPI = {
                getState: store.getState,
                dispatch: (action) => dispatch(action)
              };

              chain = middlewares.map(middleware =>
                            middleware(middlewareAPI));
              dispatch = compose(...chain, store.dispatch);
              return {
                ...store,
                dispatch
              };
           };
}

applyMiddleware源碼使用了柯里化(理解:將多參數(shù)函數(shù)轉(zhuǎn)化為單參數(shù)函數(shù)執(zhí)行父虑,然后返回結(jié)果接受剩下的參數(shù)繼續(xù)執(zhí)行)。
柯里化的過程存在閉包授药,因而store是最新并且相同的士嚎。

//  柯里化簡單示例
function add(x){
   return function(y){
      return x+y;
   }
}
console.log(add(5)(10)); // 15

對于中間件的理解:同步action:action發(fā)出后,reducer立即算出state悔叽。異步action:action發(fā)出以后莱衩,過一段時間再執(zhí)行reducer。怎樣是reducer在異步操作結(jié)束后自動執(zhí)行娇澎?使用的解決方法就是:中間件笨蚁。

同步action只要發(fā)出一種action,異步action一般要發(fā)出三種action。

異步action一般要發(fā)出的三種action:

  1. 操作發(fā)起時的action
  2. 操作成功時的action
  3. 操作失敗時的action

同步action的執(zhí)行流程:

action-->dispatch-->reducers

當我們執(zhí)行異步action的時候可能還需要在action傳遞到reducer這個過程中處理一些其他的事趟庄,執(zhí)行一些額外的函數(shù)括细。
執(zhí)行流程:

action-->dispatch-->額外的函數(shù)代碼-->reducer

引入中間件,將額外需要執(zhí)行的函數(shù)放到中間件中執(zhí)行

action-->dispatch-->middleware1-->...-->middleware3-->reducer

我們都知道action creator是一個純js函數(shù)岔激,返回的是一個對象勒极。將action creator通過dispacth傳遞到reducer從而改變state.
而通過使用中間件,action creator返回的將是一個函數(shù)(或者其他的類似Promise對象等)虑鼎,返回的函數(shù)的參數(shù)是dispatch和getState這個兩個redux方法辱匿。執(zhí)行異步請求的時候,第一個action表示操作開始炫彩,該操作類似同步action操作匾七。第二個action的發(fā)出是在返回的函數(shù)中(因為返回的函數(shù)的參數(shù)含有g(shù)etState,所有state是最新的江兢。而參數(shù)中又含有dispatch昨忆,所有可以通過該方法執(zhí)行第二個action)。

參考資料一;參考資料二;
參考資料三

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末杉允,一起剝皮案震驚了整個濱河市邑贴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌叔磷,老刑警劉巖拢驾,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異改基,居然都是意外死亡繁疤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來稠腊,“玉大人躁染,你說我怎么就攤上這事〖芗桑” “怎么了吞彤?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鳖昌。 經(jīng)常有香客問我备畦,道長,這世上最難降的妖魔是什么许昨? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任懂盐,我火速辦了婚禮,結(jié)果婚禮上糕档,老公的妹妹穿的比我還像新娘莉恼。我一直安慰自己,他們只是感情好速那,可當我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布俐银。 她就那樣靜靜地躺著,像睡著了一般端仰。 火紅的嫁衣襯著肌膚如雪捶惜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天荔烧,我揣著相機與錄音吱七,去河邊找鬼。 笑死鹤竭,一個胖子當著我的面吹牛踊餐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播臀稚,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼吝岭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了吧寺?” 一聲冷哼從身側(cè)響起窜管,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎稚机,沒想到半個月后微峰,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡抒钱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谋币。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡仗扬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蕾额,到底是詐尸還是另有隱情早芭,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布诅蝶,位于F島的核電站退个,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏调炬。R本人自食惡果不足惜语盈,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缰泡。 院中可真熱鬧刀荒,春花似錦、人聲如沸棘钞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宜猜。三九已至泼返,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間姨拥,已是汗流浹背绅喉。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留垫毙,地道東北人霹疫。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像综芥,于是被迫代替她去往敵國和親丽蝎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,691評論 2 361

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