Redux初步理解

Redux筆記

參考理解

Redux 中文文檔
Redux 阮一峰

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

Redux簡單概括:單一的state是存儲在store中,當(dāng)要對state進(jìn)行更新的時(shí)候,首先要發(fā)起一個(gè)action(通過dispatch

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

從不直接修改 state 是 Redux 的核心理念之一, 所以你會發(fā)現(xiàn)自己總是在使用 Object.assign() 創(chuàng)建對象拷貝, 而拷貝中會包含新創(chuàng)建或更新過的屬性值。在下面的 todoApp 示例中, Object.assign() 將會返回一個(gè)新的 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. 用戶的使用方式復(fù)雜(Ul界面復(fù)雜,操作內(nèi)容多)
  2. 不同身份的用戶有不同的使用方式(普通用戶和管理員)
  3. 多個(gè)用戶之間可以協(xié)作
  4. 與服務(wù)器有大量交互
  5. View要從多個(gè)來源獲取數(shù)據(jù)

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

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

要點(diǎn)

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

store

store 充當(dāng)一個(gè)容器,用來保存數(shù)據(jù)的地方。也可以理解成存儲state的地方。整個(gè)應(yīng)用 只能 有一個(gè)Store
由于整個(gè)應(yīng)用只有一個(gè)store,所以store保存了所有的數(shù)據(jù)。可以通過store.getState()獲取當(dāng)前時(shí)刻的state.

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

action

Action 本質(zhì)上是 JavaScript 普通對象。我們約定,action 內(nèi)必須使用一個(gè)字符串類型的 type 字段來表示將要執(zhí)行的動作。多數(shù)情況下,type 會被定義成字符串常量。當(dāng)應(yīng)用規(guī)模越來越大時(shí),建議使用單獨(dú)的模塊或文件來存放 action。
action是由用戶操作view產(chǎn)生的。view的改變產(chǎn)生action,action的改變會傳到store,進(jìn)而影響state的改變。

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

reducer

store 接收到action后,必須給出一個(gè)新的state,從而是view做出變化。這樣的一個(gè)計(jì)算過程叫做Reducer.
Reducer是一個(gè) 函數(shù),接收action和當(dāng)前的state作為參數(shù),然后返回一個(gè)新的state.

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

因?yàn)镽educer函數(shù)負(fù)責(zé)生成state,而整個(gè)應(yīng)用只有一個(gè)state,所以當(dāng)state非常大的時(shí)候,導(dǎo)致Reduce函數(shù)也非常的大,
根據(jù)action不同的種類,我們可以將reducer拆分為多個(gè)小的reducer,最后再合成一個(gè)大的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;
}};
上面這個(gè)Reducer包含了3個(gè)action,顯得比較大,比較臃腫。
我們可以對其進(jìn)行拆分函數(shù):
const chatReducer = (state = defaultState, action = {}) => {
return {
chatLog: chatLog(state.chatLog, action),
statusMessage: statusMessage(state.statusMessage, action),
userName: userName(state.userName, action)
}};
這樣一個(gè)大的Reducer函數(shù)就拆分成三個(gè)小函數(shù),每個(gè)小函數(shù)負(fù)責(zé)生成對應(yīng)屬性。這樣就可以把小的函數(shù)理解成子reducer函數(shù)。
這就與react的根組件與子組件的概念吻合,根組件對應(yīng)最終生成的大的Reducer,而子組件對應(yīng)每個(gè)子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()來實(shí)現(xiàn)上面todoApp做的事情
import { combineReducers } from 'redux';
  const todoApp = combineReducers({
  visibilityFilter,
  todos
 })
 上面的代碼等價(jià)于下面的代碼
 export default function todoApp(state = {}, action) {
   return {
     visibilityFilter: visibilityFilter(state.visibilityFilter, action),
     todos: todos(state.todos, action)
  }}

export default todoApp;

Redux提供combineReducers方法,用于將定義的各個(gè)子Reducer函數(shù)合并成一個(gè)大的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)建一個(gè) store ,每當(dāng)我們在 store 上 dispatch 一個(gè) action ,store 內(nèi)的數(shù)據(jù)就會相應(yīng)地發(fā)生變化。

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

正常的思路應(yīng)該是view發(fā)出action通過dispatch()傳遞給reducer進(jìn)行相關(guān)的計(jì)算從而得出新的state。觀察上面創(chuàng)建store的代碼,可以發(fā)現(xiàn)我們是
先把reducer這個(gè)計(jì)算函數(shù)“放入”store里面,所以我們就可以實(shí)現(xiàn)當(dāng)我們把a(bǔ)ction通過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)的任何一個(gè)組件(比如這里的 Comp ),如果需要使用 state 中的數(shù)據(jù),就必須是「被 connect 過的」組件——使用 connect 方法對「你編寫的組件( MyComp )」進(jìn)行包裝后的產(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的第一個(gè)參數(shù)是mapStateToProps函數(shù), 該函數(shù)允許我們將我們將store中的數(shù)據(jù)作為props綁定到組件上。

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

使用ownProps示例:(比如點(diǎn)擊人員列表查看人員詳細(xì)信息,點(diǎn)擊事件傳遞組件(該組件只維護(hù)一個(gè)用戶信息)人員Id屬性props,然后可以通過這個(gè)組件自己的props去store獲取對應(yīng)數(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);

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

mapDispatchToProps(dispatch, ownProps): dispatchProps

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

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

代碼示例

import React, {Component} from 'react'
class Counter extends Component {
    render() {
        //從組件的props屬性中導(dǎo)入四個(gè)方法和一個(gè)變量
        const {increment, decrement, counter} = this.props;
        //渲染組件,包括一個(gè)數(shù)字,四個(gè)按鈕
        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ā)起之后,到達(dá)reducer之前的擴(kuò)展點(diǎn)

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

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

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

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

項(xiàng)目中使用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ā)出以后,過一段時(shí)間再執(zhí)行reducer。怎樣是reducer在異步操作結(jié)束后自動執(zhí)行?使用的解決方法就是:中間件。

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

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

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

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

action-->dispatch-->reducers

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

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

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

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

我們都知道action creator是一個(gè)純js函數(shù),返回的是一個(gè)對象。將action creator通過dispacth傳遞到reducer從而改變state.
而通過使用中間件,action creator返回的將是一個(gè)函數(shù)(或者其他的類似Promise對象等),返回的函數(shù)的參數(shù)是dispatch和getState這個(gè)兩個(gè)redux方法。執(zhí)行異步請求的時(shí)候,第一個(gè)action表示操作開始,該操作類似同步action操作。第二個(gè)action的發(fā)出是在返回的函數(shù)中(因?yàn)榉祷氐暮瘮?shù)的參數(shù)含有g(shù)etState,所有state是最新的。而參數(shù)中又含有dispatch,所有可以通過該方法執(zhí)行第二個(gè)action)。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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