本文主要講解redux的核心原理以及相關的周邊技術原理。幫助你在使用的時候,知其因此然~javascript
涉及:redux、redux異步解決方法、中間件、react-redux原理、Dvahtml
做爲狀態容器,提供對狀態的查詢、改變
進行管理。
從具體操做
-> 狀態變動
-> 觸發視圖更新
,這一單向數據流的控制,不只使得對狀態變化的過程變得可控,同時解耦了數據M和視圖V。java
1)應用複雜,將狀態及頁面邏輯reducer提取出來,利於代碼結構的組織;
2)全局狀態共享或可能被共享和改變的組件狀態。react
1)web應用是一個狀態機,視圖和狀態一一對應;
2)全部狀態保存在一個對象內。web
state狀態對象由store負責存儲獲取管理;
state爲不可變對象(immutable data
),保證每次返回一個新的狀態對象。json
action是改變狀態對象state的惟一方式;
action由dispatch函數觸發,其描述了一種行爲其常見形式;redux
{ type: 'actionType', //行爲 payload: {} //須要傳遞的信息 }
payload是更新store數據的惟一來源。併發
type Reducer<S, A> = (state: S, action: A) => S
對action作出響應的響應,返回新的狀態對象state(保證可回溯的緣由),不該該產生反作用;
生成新的state有以下幾種方式:app
- Object.assign({}, oldState, newState); - {...oldState, 更新值} - Immutable
javascript中的基本類型:Boolean、Number、String、Null、undefined、Symbol
等都是不可變的(Immutable
),只有Object
是可變的(Mutable
).異步
用於訂閱事件,每次state變動後,都會觸發其訂閱的事件。
這裏能夠處理state -> props,以此更新視圖組件的更新渲染(react-redux的connect就是這麼實現的
);
//該方法用於生成action let actionCreator = (name) => ({ type: 'ADD_ITEM', payload: { name } }); //生成action dispatch(actionCreator('M2'));
1)redux-thunk
其經過擴展action,使得actionCreator返回一個function做爲action。
let asyncActionCreator = postTitle => (dispatch, getState) => { dispatch(requestPosts(postTitle)); return fetch(`/some/API/${postTitle}.json`) .then(response => response.json()) .then(json => dispatch(receivePosts(postTitle, json))); }; }; //這裏須要使用middleware支持異步,例如redux-thunk var thunkMiddleware = function ({ dispatch, getState }) { return function(next) { return function (action) { //若是是function,則傳遞dispatch、getState,並執行 return typeof action === 'function' ? //原始的dispatch action(dispatch, getState) : next(action) } } } //使用1 store.dispatch(fetchPosts('xx')); // 使用2:注意上面dispatch後返回的Promise,能夠在dispatch異步數據,reducer處理後,作一些處理 store.dispatch(fetchPosts(genPromise)).then(() => console.log(store.getState()) );
thunk的應用:延遲執行、異步控制。做爲Generator的next()的返回值,Thunk和Promise都是Generator自動執行的解決方案。
2)redux-sage
dispatch({type: 'USER_FETCH_REQUESTED', payload: {userId}})
redux-saga
是一個 redux 中間件;使用了 ES6 的 Generator 功能,以便於對流程作管理。
如何使用?
// 建立saga middleware const sagaMiddleware = createSagaMiddleware(); // 注入saga middleware applyMiddleware(sagaMiddleware); //啓動 sagaMiddleWare.run(rootSaga);
先來看rootSaga的寫法:
// worker Saga : 將在 USER_FETCH_REQUESTED action 被 dispatch 時調用 function* workerSaga(action) { try { const user = yield call(Api.fetchUser, action.payload.userId); yield put({type: "USER_FETCH_SUCCEEDED", user: user}); } catch (e) { yield put({type: "USER_FETCH_FAILED", message: e.message}); } } /* 在每一個 `USER_FETCH_REQUESTED` action 被 dispatch 時調用 fetchUser 容許併發(譯註:即同時處理多個相同的 action) */ function* watcherSaga() { yield takeEvery("USER_FETCH_REQUESTED", workerSaga); } export default watcherSaga;
由上例可知,rootSaga是Generator函數,而sagaMiddleWare.run();
是其自執行器;
啓動後會執行takeEvery(),看下它作了什麼事:
function* takeEvery("USER_FETCH_REQUESTED", workerSaga) { //啓動一個新的獨立的task,執行一個構造的Generator yield fork(function* () { //註冊掛起->消費註銷,執行對應的saga->註冊掛起... while(true) { //將當前actionType註冊到發佈中心,掛起,等待dispatch時在中間件中觸發,匹配則執行該task的next()即workerSaga,同時註銷該actionType的註冊信息; yield take("USER_FETCH_REQUESTED"); //執行saga任務 yield task(workerSaga); } }); }
注意這裏的task()、take()、fork()
,包括saga中call()、put()
等返回的都是effect對象,好比call()執行後:
{ isEffect: true, type: 'CALL', fn: Api.fetchUser }
task自執行器在根據next()的返回值,判斷effect的類型作對應的操做,好比執行yield take('USER_FETCH_REQUESTED')
後,it.next()
返回的是:
{ type: 'take', pattern: 'USER_FETCH_REQUESTED' }
那麼自執行器會判斷:
//effect爲take的執行邏輯 if (effect.type === 'take') { runTakeEffect(result.value, next); } //runTakeEffect將當前Generator掛起,註冊到channel中心,等待喚醒 function runTakeEffect(effect, cb) { chan.take({cb, pattern:effect.pattern}); }
發佈訂閱中心channel的代碼以下:
function channel() { let _task = null; //掛起task function take(task) { _task = task; } //dispatch時,中間件put觸發對應的actionType,來匹配對應pattern的回調 function put(pattern, args) { if(!_task) return; if(pattern == _task.pattern) { taker = null; //僅消費一次,註銷,等待下次循環時再註冊 _task.cb.call(null, args);//喚醒掛起task;調用next()繼續控制Generator執行 } } return { take, put } }
最後來看下redux-saga中間件是到channel中匹配對應action的:
const sagaMiddleware = store => { return next => action => { next(action); const { type, ...payload } = action; //調用channel的put方法,會判斷是否匹配,匹配的話則執行掛起的task,即對應的saga channel.put(type, payload); } }
總結來講:
核心仍是內部實現的Generator自執行器,對不一樣的命令對象作不一樣的處理邏輯;
循環過程:啓動時,take掛起,等待put喚醒掛起的task調用next(),同時註銷effect;讓task自執行對應的gennerator函數。執行完後循環,再從新註冊
發佈訂閱模式:chanel發佈訂閱中心,run執行時首先執行takeEvery,註冊effect。在中間件中觸發put(action)
問題:
1.爲何不用Async做爲自執行器呢?
redux-saga內部實現了Generator自執行器,能夠自主的控制流程的暫停,運行等;
2.爲何call()返回effect對象呢?
一個是根據effect類型能夠作多種操做;再者這也是其比redux-thunk更利於測試的原理。
redux-sage相較於redux-thunk的優點:
Generator同步的寫法,實現異步流程的控制管理;
注意:let res = yield call('xx');
, res的值爲next(val)傳入的參數,而不是yield後面的表達式!
Generator經過yield
和next
來傳遞數據來控制函數的內部流程(內外部的雙向通訊
)。
洋蔥圈模型,和KOA的中間件原理相似;
在action真正處理前,賦予諸如日誌記錄等能力。
//將中間件綁定到 let applyMiddleware = middlewares => (createStore) => (...args) => { let store = createStore(...args); let dispatch = store.dispatch; let params = { dispatch, getState: store.getState }; middlewares = middlewares.map(mw => mw(params)); //組合中間件,加強dispatch //裝飾者模式 & 洋蔥圈,compose對中間件的組合 dispatch = compose(middlewares)(dispatch); //返回加強後的dispatch return Object.assign(store, {dispatch}); }
基於容器組件和展現組件相分離的開發思想,將組件分爲:UI組件
和容器組件
.
import { connect } from 'react-redux' const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(TodoList) export default VisibleTodoList;
mapStateToProps:將state映射到展現組件的props;
mapDispatchToProps: 定義了組件內部須要的事件,內部注入了dispatch;
怎麼獲取state?
connect方法生成容器組件之後,須要讓容器組件拿到state對象,才能生成 UI 組件的參數。一種方式是把它以 props 的形式傳入到全部容器組件中,存在層層繁瑣的傳遞,並且每每中間組件並不須要的問題。建議的方式是使用指定的 React Redux 組件 <Provider>
:
生產者
:<Provider>組件//使用 <Provider store={store}> <App /> </Provider> //Provider利用了React的context class Provider extends Component { getChildContext() { return { store: this.props.store }; } render() { return this.props.children; } } Provider.childContextTypes = { store: React.PropTypes.object }
消費者
:connect最後看下connect的簡單實現:
export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => { class Connect extends Component { static contextTypes = { store: PropTypes.object } constructor () { super(); this.state = { allProps: {} } } componentWillMount () { const { store } = this.context this._updateProps() store.subscribe(() => this._updateProps()) } _updateProps () { const { store } = this.context let stateProps = mapStateToProps ? mapStateToProps(store.getState(), this.props): {} let dispatchProps = mapDispatchToProps? mapDispatchToProps(store.dispatch, this.props) : {} this.setState({ allProps: { ...stateProps, ...dispatchProps, ...this.props } }) } render () { return <WrappedComponent {...this.state.allProps} /> } } return Connect }
Dva 是基於 React + Redux + Saga 的最佳實踐沉澱, 作了 3 件很重要的事情, 大大提高了編碼體驗:
model
的概念, 寫在一個 js 文件裏面;更多技術分享,歡迎【掃碼關注】~
參考:
https://zhuanlan.zhihu.com/p/...
https://zhuanlan.zhihu.com/p/...
https://blog.csdn.net/TurkeyC...
https://dvajs.com/guide/fig-s...