Redux相關原理快速掌握

本文主要講解redux的核心原理以及相關的周邊技術原理。幫助你在使用的時候,知其因此然~javascript

涉及:redux、redux異步解決方法、中間件、react-redux原理、Dvahtml

用途

做爲狀態容器,提供對狀態的查詢、改變進行管理。
具體操做 -> 狀態變動 -> 觸發視圖更新,這一單向數據流的控制,不只使得對狀態變化的過程變得可控,同時解耦了數據M和視圖Vjava

使用場景

1)應用複雜,將狀態及頁面邏輯reducer提取出來,利於代碼結構的組織;
2)全局狀態共享或可能被共享和改變的組件狀態。react

設計思想

1)web應用是一個狀態機,視圖和狀態一一對應;
2)全部狀態保存在一個對象內。web

三大原則

單一數據源

state狀態對象由store負責存儲獲取管理;
state爲不可變對象(immutable data),保證每次返回一個新的狀態對象。json

state只讀

action是改變狀態對象state的惟一方式;
action由dispatch函數觸發,其描述了一種行爲其常見形式;redux

{
 type: 'actionType',   //行爲
 payload: {}   //須要傳遞的信息
}

payload是更新store數據的惟一來源。併發

reducer是純函數
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).異步

store提供的方法:

subscribe

用於訂閱事件,每次state變動後,都會觸發其訂閱的事件。
這裏能夠處理state -> props,以此更新視圖組件的更新渲染(react-redux的connect就是這麼實現的);

dispatch
  • 處理同步action
//該方法用於生成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的優點:

  • 不改動action;
  • Generator同步的寫法,實現異步流程的控制管理;

    注意: let res = yield call('xx');, res的值爲next(val)傳入的參數,而不是yield後面的表達式!
    Generator經過 yieldnext來傳遞數據來控制函數的內部流程( 內外部的雙向通訊)。
  • 單獨的文件組織,以及yield後call等關鍵字返回的是effect對象,便於測試維護;

中間件

洋蔥圈模型,和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});
}

React-Redux

基於容器組件和展現組件相分離的開發思想,將組件分爲: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

Dva 是基於 React + Redux + Saga 的最佳實踐沉澱, 作了 3 件很重要的事情, 大大提高了編碼體驗:

  1. 把 store 及 saga 統一爲一個 model 的概念, 寫在一個 js 文件裏面;
  2. 增長了一個 Subscriptions, 用於收集其餘來源的 action, eg: 鍵盤操做;
  3. model 寫法很簡約, 相似於 DSL 或者 RoR, coding 快得飛起✈️

更多技術分享,歡迎【掃碼關注】~
1584413667(1).jpg

參考:
https://zhuanlan.zhihu.com/p/...
https://zhuanlan.zhihu.com/p/...
https://blog.csdn.net/TurkeyC...
https://dvajs.com/guide/fig-s...

相關文章
相關標籤/搜索