解讀redux工做原理

歡迎訪問我的博客的其餘文章html

1. 前言

隨着WEB應用變得愈來愈複雜,再加上node先後端分離愈來愈流行,那麼對數據流動的控制就顯得愈加重要。redux是在flux的基礎上產生的,基本思想是保證數據的單向流動,同時便於控制、使用、測試。node

redux不依賴於任意框架(庫),只要subscribe相應框架(庫)的內部方法,就可使用該應用框架保證數據流動的一致性。react

那麼如何使用redux呢?下面一步步進行解析,並帶有源碼說明,不只作到知其然,還要作到知其因此然git

2. 主幹邏輯介紹(createStore)

2.1 簡單demo入門

先來一個直觀的認識:github

// 首先定義一個改變數據的plain函數,成爲reducer
function count (state, action) {
    var defaultState = {
        year: 2015,
      };
    state = state || defaultState;
    switch (action.type) {
        case 'add':
            return {
                year: state.year + 1
            };
        case 'sub':
            return {
                year: state.year - 1
            }
        default :
            return state;
    }
}

// store的建立
var createStore = require('redux').createStore;
var store = createStore(count);

// store裏面的數據發生改變時,觸發的回調函數
store.subscribe(function () {
      console.log('the year is: ', store.getState().year);
});

// action: 觸發state改變的惟一方法(按照redux的設計思路)
var action1 = { type: 'add' };
var action2 = { type: 'add' };
var action3 = { type: 'sub' };

// 改變store裏面的方法
store.dispatch(action1); // 'the year is: 2016
store.dispatch(action2); // 'the year is: 2017
store.dispatch(action3); // 'the year is: 2016

2.2 挖掘createStore實現

爲了說明主要問題,僅列出其中的關鍵代碼,所有代碼,能夠點擊這裏閱讀。redux

a 首先看createStore到底都返回的內容:後端

export default function createStore(reducer, initialState) {
    ...
    return {
        dispatch,
        subscribe,
        getState,
        replaceReducer
    }
}

每一個屬性的含義是:閉包

  • dispatch: 用於action的分發,改變store裏面的stateapp

  • subscribe: 註冊listener,store裏面state發生改變後,執行該listener框架

  • getState: 讀取store裏面的state

  • replaceReducer: 替換reducer,改變state修改的邏輯

b 關鍵代碼解析

export default function createStore(reducer, initialState) {
    // 這些都是閉包變量
    var currentReducer = reducer
    var currentState = initialState
    var listeners = []
    var isDispatching = false;

    // 返回當前的state
    function getState() {
        return currentState
    }

    // 註冊listener,同時返回一個取消事件註冊的方法
    function subscribe(listener) {
        listeners.push(listener)
        var isSubscribed = true

        return function unsubscribe() {
            if (!isSubscribed) {
                return
            }

            isSubscribed = false
            var index = listeners.indexOf(listener)
            listeners.splice(index, 1)
        }
    }

    // 經過action該改變state,而後執行subscribe註冊的方法
    function dispatch(action) {
        try {
          isDispatching = true
              currentState = currentReducer(currentState, action)
        } finally {
              isDispatching = false
        }
        listeners.slice().forEach(listener => listener())
        return action
    }

    // 替換reducer,修改state變化的邏輯
    function replaceReducer(nextReducer) {
           currentReducer = nextReducer
           dispatch({ type: ActionTypes.INIT })
       }

       // 初始化時,執行內部一個dispatch,獲得初始state
       dispatch({ type: ActionTypes.INIT })
}

若是還按照2.1的方式進行開發,那跟flux沒有什麼大的區別,須要手動解決不少問題,那redux如何將整個流程模板化(Boilerplate)呢?

3. 保證store的惟一性

隨着應用愈來愈大,一方面,不能把全部的數據都放到一個reducer裏面,另外一方面,爲每一個reducer建立一個store,後續store的維護就顯得比較麻煩。如何將兩者統一塊兒來呢?

3.1 demo入手

經過combineReducers將多個reducer合併成一個rootReducer:

// 建立兩個reducer: count year
function count (state, action) {
  state = state || {count: 1}
  switch (action.type) {
    default:
      return state;
  }
}
function year (state, action) {
  state = state || {year: 2015}
  switch (action.type) {
    default:
      return state;
  }
}

// 將多個reducer合併成一個
var combineReducers = require('./').combineReducers;
var rootReducer = combineReducers({
  count: count,
  year: year,
});

// 建立store,跟2.1沒有任何區別
var createStore = require('./').createStore;
var store = createStore(rootReducer);

var util = require('util');
console.log(util.inspect(store));
//輸出的結果,跟2.1的store在結構上不存在區別
// { dispatch: [Function: dispatch],
//   subscribe: [Function: subscribe],
//   getState: [Function: getState],
//   replaceReducer: [Function: replaceReducer]
// }

3.2 源碼解析combineReducers

// 高階函數,最後返回一個reducer
export default function combineReducers(reducers) {
    // 提出不合法的reducers, finalReducers就是一個閉包變量
    var finalReducers = pick(reducers, (val) => typeof val === 'function')
    // 將各個reducer的初始state均設置爲undefined
    var defaultState = mapValues(finalReducers, () => undefined)

    // 一個總reducer,內部包含子reducer
    return function combination(state = defaultState, action) {
        var finalState = mapValues(finalReducers, (reducer, key) => {
            var previousStateForKey = state[key]
            var nextStateForKey = reducer(previousStateForKey, action)
            hasChanged = hasChanged || nextStateForKey !== previousStateForKey
            return nextStateForKey
        }
    }

    return hasChanged ? finalState : state

}

4. 自動實現dispatch

4.1 demo介紹

在2.1中,要執行state的改變,須要手動dispatch:

var action = { type: '***', payload: '***'};
dispatch(action);

手動dispatch就顯得囉嗦了,那麼如何自動完成呢?

var bindActionCreators = require('redux').bindActionCreators;
// 能夠在具體的應用框架隱式進行該過程(例如react-redux的connect組件中)
bindActionCreators(action)

4.2 源碼解析

// 隱式實現dispatch
function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

export default function bindActionCreators(actionCreators, dispatch) {
    if (typeof actionCreators === 'function') {
        return bindActionCreator(actionCreators, dispatch)
    }
    return mapValues(actionCreators, actionCreator =>
        bindAQctionCreator(actionCreator, dispatch)
    )
}

5. 支持插件 - 對dispatch的改造

5.1 插件使用demo

一個action能夠是同步的,也多是異步的,這是兩種不一樣的狀況, dispatch執行的時機是不同的:

// 同步的action creator, store能夠默認實現dispatch
function add() {
    return { tyle: 'add' }
}
dispatch(add());

// 異步的action creator,由於異步完成的時間不肯定,只能手工dispatch
function fetchDataAsync() {
    return function (dispatch) {
        requst(url).end(function (err, res) {
            if (err) return dispatch({ type: 'SET_ERR', payload: err});
            if (res.status === 'success') {
                dispatch({ type: 'FETCH_SUCCESS', payload: res.data });
            }
        })
    }
}

下面的問題就變成了,如何根據實際狀況實現不一樣的dispatch方法,也便是根據須要實現不一樣的moddleware:

// 普通的dispatch建立方法
var store = createStore(reducer, initialState);
console.log(store.dispatch);

// 定製化的dispatch
var applyMiddleware = require('redux').applyMiddleware;
// 實現action異步的middleware
var thunk = requre('redux-thunk');
var store = applyMiddleware([thunk])(createStore);
// 通過處理的dispatch方法
console.log(store.dispatch);

5.2 源碼解析

// next: 其實就是createStore
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 // 實現新的dispatch方法
    }
  }
}
// 再看看redux-thunk的實現, next就是store裏面的上一個dispatch
function thunkMiddleware({ dispatch, getState }) {
    return function(next) {
        return function(action) {
            typeof action === 'function' ?
            action(dispatch, getState) :
            next(action);
        }
    }
  return next => action =>
    typeof action === 'function' ?
      action(dispatch, getState) :
      next(action);
}

6. 與react框架的結合

6.1 基本使用

目前已經有現成的工具react-redux來實現兩者的結合:

var rootReducers = combineReducers(reducers);
var store = createStore(rootReducers);
var Provider = require('react-redux').Provider;
// App 爲上層的Component
class App extend React.Component{
    render() {
        return (
            <Provier store={store}>
                <Container />
            </Provider>
        );
    }
}

// Container做用: 1. 獲取store中的數據; 2.將dispatch與actionCreator結合起來
var connect = require('react-redux').connect;
var actionCreators = require('...');
// MyComponent是與redux無關的組件
var MyComponent = require('...');

function select(state) {
    return {
        count: state.count
    }
}
export default connect(select, actionCreators)(MyComponent)

6.2 Provider -- 提供store

React經過Context屬性,能夠將屬性(props)直接給子孫component,無須經過props層層傳遞, Provider僅僅起到得到store,而後將其傳遞給子孫元素而已:

export default class Provider extends Component {
  getChildContext() { // getChildContext: 將store傳遞給子孫component
    return { store: this.store }
  }

  constructor(props, context) {
    super(props, context)
    this.store = props.store
  }

  componentWillReceiveProps(nextProps) {
    const { store } = this
    const { store: nextStore } = nextProps

    if (store !== nextStore) {
      warnAboutReceivingStore()
    }
  }

  render() {
    let { children } = this.props
    return Children.only(children)
  }
}

6.3 connect -- 得到store及dispatch(actionCreator)

connect是一個高階函數,首先傳入mapStateToProps、mapDispatchToProps,而後返回一個生產Component的函數(wrapWithConnect),而後再將真正的Component做爲參數傳入wrapWithConnect(MyComponent),這樣就生產出一個通過包裹的Connect組件,該組件具備以下特色:

  • 經過this.context獲取祖先Component的store

  • props包括stateProps、dispatchProps、parentProps,合併在一塊兒獲得nextState,做爲props傳給真正的Component

  • componentDidMount時,添加事件this.store.subscribe(this.handleChange),實現頁面交互

  • shouldComponentUpdate時判斷是否有避免進行渲染,提高頁面性能,並獲得nextState

  • componentWillUnmount時移除註冊的事件this.handleChange

  • 在非生產環境下,帶有熱重載功能

    // 主要的代碼邏輯
    export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {

    return function wrapWithConnect(WrappedComponent) {
         class Connect extends Component {
               constructor(props, context) {
                   // 從祖先Component處得到store
                   this.store = props.store || context.store
                   this.stateProps = computeStateProps(this.store, props)
                   this.dispatchProps = computeDispatchProps(this.store, props)
                   this.state = { storeState: null }
                   // 對stateProps、dispatchProps、parentProps進行合併      
                   this.updateState()
               }
               shouldComponentUpdate(nextProps, nextState) {
                   // 進行判斷,當數據發生改變時,Component從新渲染
                   if (propsChanged || mapStateProducedChange || dispatchPropsChanged) {
                     this.updateState(nextProps)
                     return true
                   }
               }
               componentDidMount() {
                   // 改變Component的state
                 this.store.subscribe(() = {
                     this.setState({
                       storeState: this.store.getState()
                     })
                 })
               }
               render() {
                   // 生成包裹組件Connect
                 return (
                   <WrappedComponent {...this.nextState} />
                 )
               }
           }
           Connect.contextTypes = {
             store: storeShape
           }
           return Connect;
       }

    }

7. redux與react-redux關係圖

圖片描述

相關文章
相關標籤/搜索