Redux and Vuex 源碼分析

Redux and Vuex 源碼分析

一 全局狀態管理框架的產生

Redux和Vuex都是全局狀態管理框架,我以爲他們的出現有四個緣由:html

  • 第一,是SPA,若是不是SPA,多頁面之間也就不存在複雜的交互,每打開一個頁面表明新的生命週期,頁面之間的交互能夠利用window對象和URL參數,固然也存在一個頁面的組件之間有複雜的交互,可是Redux和Vuex確實是更適用於SPA的場景,頁面做爲組件,頁面之間能夠共同管理數據狀態,更像是一個應用,Redux和Vuex管理着這個應用的全部狀態。vue

  • 第二,是組件化,我認爲組件化有兩種,第一種是與業務邏輯無關的,能夠複用的組件,第二種是業務邏輯的組件,提取出來是爲了代碼的可維護性,可讀性。組件化以後,一個頁面可能有幾個甚至十幾個組件組成,並且還可能出現幾層嵌套的狀況,這就產生了組件之間共同管理狀態的場景。react

  • 第三,是複雜的交互邏輯,有了SPA有了組件化,再出現複雜的交互邏輯,須要多頁面多組件之間的數據共享。若是是同一頁面內,在沒有管理框架的時候咱們都是把共享狀態放在根組件,而後將利用屬性下傳狀態和管理方法,或者利用全局事件的方式。全局事件多了管理混亂,傳屬性的話,可能出現代碼重複,而且也是管理不規範。git

  • 第四,是數據與視圖的綁定,數據驅動視圖的更新的方式,使得數據的管理相當重要,數據決定了視圖的展現,因此對於數據的管理須要一套規範。github

二 Redux源碼分析

具體的使用和概念參見Redux中文文檔 http://cn.redux.js.org//index...。本文從源碼的角度進行分析。vuex

2.1 redux源碼的結構介紹

  • index.js:源碼的入口文件,集合導出其他5個文件的功能。redux

  • createStore.js: 建立store對象的方法,接受reducer和initState。react-native

  • applyMiddle.js: 應用中間件的方法,該方法接受中間件數組,起到包裝store的dispatch方法的做用,在action到達reducer以前能夠作一些操做。數組

  • combineReducers.js: 組合reducer的方法,將多個reducer組合成一個reducer,redux中對於state的劃分就是利用reducer的劃分, combineReducers方法將多個reducer合成一個reducer方法,也將多個reducer的state合成一個全局的state,每個reducer只能操做自身的state。promise

  • bindActionCreators.js: 提供了一個幫助方法,對actionCreator方法利用dispatch再進行一次包裝,包裝成的方法能夠直接觸發dispatch。

2.2 createStore.js文件

createStore.js文件提供了建立store的方法,下面只顯示一些加了註釋的關鍵代碼部分。

  • currentState: 內部的state對象

  • currentReducer: 接受action的reducer對象

  • currentListener: 存放監聽函數的數組

  • getState: 閉包方法獲取內部的state

  • subscribe: 提供訂閱監聽的方法

  • dispatch: 接受action,將action傳遞給reducer,將返回值付給state,而且觸發監聽函數的方法

  • replaceReducer: 替換reducer的方法。

export default function createStore(reducer, preloadedState, enhancer) {
  
  var currentReducer = reducer
  var currentState = preloadedState
  var currentListeners = []
  var nextListeners = currentListeners
  var isDispatching = false

  //獲取state的方法
  function getState() {
    return currentState
  }

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }
  //提供訂閱監聽的方法
  function subscribe(listener) {

    var isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

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

      isSubscribed = false

      ensureCanMutateNextListeners()
      var index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  //將action和currentState傳入currentReducer,並將返回值賦值給currentState
  function dispatch(action) {
    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    //調用監聽函數
    var listeners = currentListeners = nextListeners
    for (var i = 0; i < listeners.length; i++) {
      listeners[i]()
    }

    return action
  }

  //總體替換reduer
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
    dispatch({ type: ActionTypes.INIT })
  }

 

  // When a store is created, an "INIT" action is dispatched so that every
  // reducer returns their initial state. This effectively populates
  // the initial state tree.
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}

2.3 combineReducers.js文件

提供了組合reducer的方法,將多個reducer組合成一個reducer,redux中對於state的劃分就是利用reducer的劃分, combineReducers方法將多個reducer合成一個reducer方法,也將多個reducer的state合成一個全局的state,每個reducer只能操做自身的state。

  • finalReducers: 最終的reducers對象

  • finalReducerKeys: 最終的reducers的key值數組

  • combination: 最終返回的組合的reducer方法

關鍵的combination代碼中,能夠獲得幾點心得:

  • 1 每個reducer只能拿到本身的子state

  • 2 全局的state是由子state組成的,若是初始的state是空的話,那麼只有在reducer被第一次調用的時候纔會賦值

  • 3 若是想改變state,由於是值比較,因此在reducer中須要返回新的state對象,同時若是全局的state變化,也會返回新的對象

//接受reduers對象,
export default function combineReducers(reducers) {
  var reducerKeys = Object.keys(reducers)
  var finalReducers = {}
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i]

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  var finalReducerKeys = Object.keys(finalReducers)

  //最後返回的組合reducer函數,接受初始的state和action
  return function combination(state = {}, action) {

    var hasChanged = false
    //新的全局state
    var nextState = {}
    //遍歷每個reducer
    for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      var reducer = finalReducers[key]
      //該reducer上的子state,若是建立store的時候沒有傳state,則是空的
      var previousStateForKey = state[key]
      //真正調用reducer函數返回state的地方
      //能夠看到reducer中的state只是本身的state,不是全局的state
      var nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        var errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      //將返回的新state放入新的全局nextState
      nextState[key] = nextStateForKey
      //是否改變比較的是state的值,因此咱們在寫reducer的時候,若是須要改變state
      //應該返回一個新的對象,若是沒有改變的話,應該返回傳給reducer的舊state對象
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    //若是有一個子state變化,那麼就返回新的state對象,這裏也是返回的新對象nextState,而不是
    //在原來的state上進行修改
    return hasChanged ? nextState : state
  }
}

2.4 applyMiddleware.js文件

提供能夠插入中間件的方法,應用中間件的目的是包裝dispatch,在action傳遞給dispatch執行以前,須要通過中間件的層層處理,進行一些業務上的處理,決定action的走向。

import compose from './compose'

/**
中間件的格式
({dispatch, getState}) =>{
    return next =>{
        return action =>{
        
        }
    }
}
 */
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    //拿到store
    var store = createStore(reducer, preloadedState, enhancer)
    var dispatch = store.dispatch
    var chain = []

    var middlewareAPI = {
      getState: store.getState,
      //這裏包一下dispatch的緣由是,讓傳給中間件的dispatch是包裝了中間件以後的dispatch,而不是原始的dispatch
      //若是寫成dispatch:diapatch,那麼當dispatch變化時,這裏的dispatch仍是原始的dispatch
      dispatch: (action) => dispatch(action) 
    }
    
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    /*
      chain鏈中的元素格式
      next =>{
        return action =>{
        
        }
      }
    */

    //利用compose函數拿到包裝了中間件的dispatch
    dispatch = compose(...chain)(store.dispatch) 

    return {
      ...store,
      dispatch
    }
  }
}

2.5 compose.js文件,提供了組合中間件的方法

/**
* compose函數最終返回的結果
* (...args) => middle1(middle2(middle3(...args))).
* 其中middle的格式
* next =>{
     return action =>{
     
     }
   }
*/
export default function compose(...funcs) {
 if (funcs.length === 0) {
   return arg => arg
 }

 if (funcs.length === 1) {
   return funcs[0]
 }
 
 const last = funcs[funcs.length - 1]
 const rest = funcs.slice(0, -1)
 //從右向左遞歸調用
 return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}

2.6 bindActionCreators.js文件,提供了綁定actoinCreator的方法

//添加dispatch的方法
function bindActionCreator(actionCreator, dispatch) {
  //返回的函數,接受參數,傳遞給actionCreator調用,actionCreator返回標準的action,而後返回dispatch的結果
  return (...args) => dispatch(actionCreator(...args))
}

//將actionCreators綁定上dispatch,key仍是actionCreators的key,可是多作了一層dispatch
export default function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error(
      `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
      `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
    )
  }

  var keys = Object.keys(actionCreators)
  var boundActionCreators = {}
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i]
    var actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

以上是Redux中6個文件的分析,下面就寫一個簡單的例子,看一下redux最基本的使用,理解下redux。

2.7 redux簡單demo

store.js

import {createStore} from 'redux';
//建立action的方法
export function createAction(type, payload) {
    return {
        type,
        payload
    }
}
//初始的state
const initialState = {
    time: new Date().getTime()
}
//reducer函數
function reducer(state = initialState, action) {
    switch (action.type) {
        case 'NOW_TIME':
            return {
                ...state,
                time: action.payload
            }
        default:
            return state;
    }
}

let store;
//獲取store的方法
export function getStore() {
    if(store) return store;
    return store = createStore(reducer);
}

testRedux.js react-native的一段代碼

'use strict';

import React, { Component } from 'react';

import {
      StyleSheet,
      View,
      Text
} from 'react-native';
import MtButton from '@scfe/react-native-button';
import {getStore, createAction} from './store';
//獲取到store
const store = getStore();
class TestRedux extends Component {
    constructor(props) {
          super(props);
        let state = store.getState();
          this.state = {
              time: state.time
          };
          //這裏訂閱state的變化,state變化以後拿到新的state,而後從新setState,更新視圖
          store.subscribe(()=>{
              let state = store.getState();
              this.setState({
                  time: state.time
              });
          });
    }
    //調用dispatch的方法
    _sendAction() {
        let action = createAction('NOW_TIME', new Date().getTime());
        store.dispatch(action);
    }
      render() {
        return (
              <View style={styles.container}>
                  <Text>{this.state.time}
                  </Text>
                <MtButton text="發出action" onPress={this._sendAction.bind(this)} /> 
              </View>
        );
      }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        padding: 40
    }
});

export default TestRedux;

固然咱們在實際生產中確定不會這樣用,還須要依賴react-redux、create-action等必要的模塊,下面就繼續看一下相關的模塊。

三 Redux相關庫源碼分析

3.1 react-actions

react-actions提供了一種靈活的建立符合FSA標準action的方法,其中的createAction.js是咱們生產中經常使用的文件,關鍵代碼以下:

'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = createAction;

var _identity = require('lodash/identity');

var _identity2 = _interopRequireDefault(_identity);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
 * 
 * @param type  action中的type,String類型
 * @param payloadCreator  建立action中的payload的函數
 * @param metaCreator  建立action中的payload的函數
 */
function createAction(type, payloadCreator, metaCreator) {
  // _identity2.default 函數返回第一個參數
  var finalPayloadCreator = typeof payloadCreator === 'function' ? payloadCreator : _identity2.default;

  //調用createAction返回一個actionHandler函數,再調用actionHandler才返回action對象
  var actionHandler = function actionHandler() {
    var hasError = (arguments.length <= 0 ? undefined : arguments[0]) instanceof Error;

    //最終返回的action對象
    var action = {
      type: type
    };

    //若是在createAction中傳payloadCreator和metaCreator函數,那麼在actionHandler中傳的參數將傳遞給
    //payloadCreator和metaCreator函數,而且將payloadCreator的返回結果當作action的payload,將metaCreator的返回結果當作action的meta
    var payload = hasError ? arguments.length <= 0 ? undefined : arguments[0] : finalPayloadCreator.apply(undefined, arguments);
    if (!(payload === null || payload === undefined)) {
      action.payload = payload;
    }

    if (hasError) {
      // Handle FSA errors where the payload is an Error object. Set error.
      action.error = true;
    }
    //將metaCreator的返回結果當作action的meta
    if (typeof metaCreator === 'function') {
      action.meta = metaCreator.apply(undefined, arguments);
    }
    //返回action
    return action;
  };

  actionHandler.toString = function () {
    return type.toString();
  };

  return actionHandler;
}

3.2 createAction方法使用實例

types.js 一般咱們把action的type統一放在一塊兒

export const GET_POI_INFO = 'GET_POI_INFO'
export const CHANGE_POI_STATUS = 'CHANGE_POI_STATUS'

actions.js actions.js用於調用createAction產生actionHandler。

import {createAction} from 'redux-actions';
import * as types from './actioins';
/**
 * 須要利用payloadCreator接收參數產生payload的action,這裏payload返回的是一個Promise,
 * 接下來會講到redux-promise中間件用於處理payload是promise的狀況,
 * 實現了在createAction的時候可以處理異步產生action的狀況
 * */
export const getPoiInfo = createAction(types.GET_POI_INFO, async(poiId)=> {
    return await poiService.getPoiInfo(poiId)
        .then(data=> {
            if (data == null) throw 'poi info is null';
            return data;
        });
});
//不須要利用payloadCreator產生payload的action
export const changePoiStatus = createAction(types.CHANGE_POI_STATUS);

3.3 redux-promise

redux-promise中間件是用於解決異步的action。

'use strict';

exports.__esModule = true;

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

exports['default'] = promiseMiddleware;

var _fluxStandardAction = require('flux-standard-action');

function isPromise(val) {
  return val && typeof val.then === 'function';
}

//_ref接收的參數對象有兩個 {getState,dispatch}
function promiseMiddleware(_ref) {
  var dispatch = _ref.dispatch;

  return function (next) {
    return function (action) {
      //若是不是標準的action
      if (!_fluxStandardAction.isFSA(action)) {
        //若是是action是promise,直接dispatch這個promise的結果,若是不是promise交給下一個中間件
        return isPromise(action) ? action.then(dispatch) : next(action);
      }
      //若是payload是promise,則把promise的結果做爲這個action的payload,而後dispatch這個action
      //不然交給下一個中間件
      return isPromise(action.payload) ? action.payload.then(function (result) {
        return dispatch(_extends({}, action, { payload: result }));
      }, function (error) {
        return dispatch(_extends({}, action, { payload: error, error: true }));
      }) : next(action);
    };
  };
}

module.exports = exports['default'];

dispatch(new Promise(){
  resolve(action)
})

有了reduex-promise中間件之後咱們就可讓action的payload是promise,而後promise的返回值是paylaod或者直接action就是一個promise,直接dispatch promise的返回值

3.4 react-redux

以上都是redux自己相關的庫,react-redux是把redux更好的結合應用於react的庫。
index.js 入口文件,向外提供了Provider和connect兩個對象。

  • Provider 是一個react的組件,接受store做爲屬性,而後放在context中,提供給子組件。咱們把它做爲根組件使用。

Provider.js

import { Component, PropTypes, Children } from 'react'
import storeShape from '../utils/storeShape'
import warning from '../utils/warning'


 //繼承react的組件
export default class Provider extends Component {
  //利用context傳遞store,子組件在構造函數中能夠經過context.store拿到store
  getChildContext() {
    return { store: this.store }
  }

  constructor(props, context) {
    super(props, context)
    this.store = props.store
  }
  //渲染惟一的子組件
  render() {
    const { children } = this.props
    return Children.only(children)
  }
}

if (process.env.NODE_ENV !== 'production') {
  Provider.prototype.componentWillReceiveProps = function (nextProps) {
    const { store } = this
    const { store: nextStore } = nextProps
    //store變化時給出警告
    if (store !== nextStore) {
      warnAboutReceivingStore()
    }
  }
}
  • connect 方法實現將自定義將store中的state映射到組件的props上,把createAction方法包裝成dispatch的方法掛在組件的props上,而且監聽store中state的變化,更新組件的props。

connect.js

調用connect最終會返回包裝的組件,在組件mounted的時候調用trySubscribe,訂閱store中state的變化,在handleChange方法中經過this.setState({ storeState })觸發從新渲染組件,在render中,調用

  • updateStatePropsIfNeeded

  • updateDispatchPropsIfNeeded

  • updateMergedPropsIfNeeded

三個方法將更新最終的this.mergeProps

import { Component, createElement } from 'react'
import storeShape from '../utils/storeShape'
import shallowEqual from '../utils/shallowEqual'
import wrapActionCreators from '../utils/wrapActionCreators'
import warning from '../utils/warning'
import isPlainObject from 'lodash/isPlainObject'
import hoistStatics from 'hoist-non-react-statics'
import invariant from 'invariant'

const defaultMapStateToProps = state => ({}) // eslint-disable-line no-unused-vars
const defaultMapDispatchToProps = dispatch => ({ dispatch })
//更新屬性 4
const defaultMergeProps = (stateProps, dispatchProps, parentProps) => ({
  ...parentProps,
  ...stateProps,
  ...dispatchProps
})


export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
  const shouldSubscribe = Boolean(mapStateToProps)
  const mapState = mapStateToProps || defaultMapStateToProps

  let mapDispatch
  if (typeof mapDispatchToProps === 'function') {
    mapDispatch = mapDispatchToProps
  } else if (!mapDispatchToProps) {
    mapDispatch = defaultMapDispatchToProps
  } else {
    mapDispatch = wrapActionCreators(mapDispatchToProps)
  }
//更新屬性 3
  const finalMergeProps = mergeProps || defaultMergeProps
  const { pure = true, withRef = false } = options
  const checkMergedEquals = pure && finalMergeProps !== defaultMergeProps

  // Helps track hot reloading.
  const version = nextVersion++

  //connect函數返回的包裝組件的方法
  return function wrapWithConnect(WrappedComponent) {
    const connectDisplayName = `Connect(${getDisplayName(WrappedComponent)})`

    function checkStateShape(props, methodName) {
      if (!isPlainObject(props)) {
        warning(
          `${methodName}() in ${connectDisplayName} must return a plain object. ` +
          `Instead received ${props}.`
        )
      }
    }

    //計算mergeProps
    function computeMergedProps(stateProps, dispatchProps, parentProps) {
      const mergedProps = finalMergeProps(stateProps, dispatchProps, parentProps)
      if (process.env.NODE_ENV !== 'production') {
        checkStateShape(mergedProps, 'mergeProps')
      }
      return mergedProps
    }

    class Connect extends Component {
      shouldComponentUpdate() {
        return !pure || this.haveOwnPropsChanged || this.hasStoreStateChanged
      }

      constructor(props, context) {
        super(props, context)
        this.version = version
        //獲取store
        this.store = props.store || context.store

        invariant(this.store,
          `Could not find "store" in either the context or ` +
          `props of "${connectDisplayName}". ` +
          `Either wrap the root component in a <Provider>, ` +
          `or explicitly pass "store" as a prop to "${connectDisplayName}".`
        )

        const storeState = this.store.getState()
        this.state = { storeState }
        this.clearCache()
      }
       //計算stateProps
      computeStateProps(store, props) {
        if (!this.finalMapStateToProps) {
          return this.configureFinalMapState(store, props)
        }

        const state = store.getState()
        //獲取state中的內容爲props
        const stateProps = this.doStatePropsDependOnOwnProps ?
          this.finalMapStateToProps(state, props) :
          this.finalMapStateToProps(state)

        if (process.env.NODE_ENV !== 'production') {
          checkStateShape(stateProps, 'mapStateToProps')
        }
        return stateProps
      }

      configureFinalMapState(store, props) {
        const mappedState = mapState(store.getState(), props)
        const isFactory = typeof mappedState === 'function'

        this.finalMapStateToProps = isFactory ? mappedState : mapState
        this.doStatePropsDependOnOwnProps = this.finalMapStateToProps.length !== 1

        if (isFactory) {
          return this.computeStateProps(store, props)
        }

        if (process.env.NODE_ENV !== 'production') {
          checkStateShape(mappedState, 'mapStateToProps')
        }
        return mappedState
      }
      //計算dispatchProps
      computeDispatchProps(store, props) {
        if (!this.finalMapDispatchToProps) {
          return this.configureFinalMapDispatch(store, props)
        }

        const { dispatch } = store
        const dispatchProps = this.doDispatchPropsDependOnOwnProps ?
          this.finalMapDispatchToProps(dispatch, props) :
          this.finalMapDispatchToProps(dispatch)

        if (process.env.NODE_ENV !== 'production') {
          checkStateShape(dispatchProps, 'mapDispatchToProps')
        }
        return dispatchProps
      }

      configureFinalMapDispatch(store, props) {
        const mappedDispatch = mapDispatch(store.dispatch, props)
        const isFactory = typeof mappedDispatch === 'function'

        this.finalMapDispatchToProps = isFactory ? mappedDispatch : mapDispatch
        this.doDispatchPropsDependOnOwnProps = this.finalMapDispatchToProps.length !== 1

        if (isFactory) {
          return this.computeDispatchProps(store, props)
        }

        if (process.env.NODE_ENV !== 'production') {
          checkStateShape(mappedDispatch, 'mapDispatchToProps')
        }
        return mappedDispatch
      }

      //更新stateProps的地方
      updateStatePropsIfNeeded() {
        const nextStateProps = this.computeStateProps(this.store, this.props)
        if (this.stateProps && shallowEqual(nextStateProps, this.stateProps)) {
          return false
        }

        this.stateProps = nextStateProps
        return true
      }
      //更新dispatchProps的地方
      updateDispatchPropsIfNeeded() {
        const nextDispatchProps = this.computeDispatchProps(this.store, this.props)
        if (this.dispatchProps && shallowEqual(nextDispatchProps, this.dispatchProps)) {
          return false
        }

        this.dispatchProps = nextDispatchProps
        return true
      }

      //更新mergeProps的地方
      updateMergedPropsIfNeeded() {
        //mergeProps由 this.stateProps, this.dispatchProps, this.props 組成
        const nextMergedProps = computeMergedProps(this.stateProps, this.dispatchProps, this.props)
        if (this.mergedProps && checkMergedEquals && shallowEqual(nextMergedProps, this.mergedProps)) {
          return false
        }

        this.mergedProps = nextMergedProps
        return true
      }

      isSubscribed() {
        return typeof this.unsubscribe === 'function'
      }
      
      //訂閱store中state的變化
      trySubscribe() {
        //
        if (shouldSubscribe && !this.unsubscribe) {
          //訂閱store的state變化
          this.unsubscribe = this.store.subscribe(this.handleChange.bind(this))
          this.handleChange()
        }
      }

      tryUnsubscribe() {
        if (this.unsubscribe) {
          this.unsubscribe()
          this.unsubscribe = null
        }
      }

      componentDidMount() {
        ////訂閱store的state變化
        this.trySubscribe()
      }

      componentWillReceiveProps(nextProps) {
        if (!pure || !shallowEqual(nextProps, this.props)) {
          this.haveOwnPropsChanged = true
        }
      }

      componentWillUnmount() {
        this.tryUnsubscribe()
        this.clearCache()
      }

      //store變化調用的方法
      handleChange() {
        if (!this.unsubscribe) {
          return
        }

        //reducer每次返回的state也是新的對象
        const storeState = this.store.getState()
        const prevStoreState = this.state.storeState
        if (pure && prevStoreState === storeState) {
          return
        }

        if (pure && !this.doStatePropsDependOnOwnProps) {
          const haveStatePropsChanged = tryCatch(this.updateStatePropsIfNeeded, this)
          if (!haveStatePropsChanged) {
            return
          }
          if (haveStatePropsChanged === errorObject) {
            this.statePropsPrecalculationError = errorObject.value
          }
          this.haveStatePropsBeenPrecalculated = true
        }

        this.hasStoreStateChanged = true
        //設置state從新渲染組件
        this.setState({ storeState })
      }

      

      render() {
        
        if (withRef) {
          // this.mergedProps  是最終給組件的屬性
          this.renderedElement = createElement(WrappedComponent, {
            ...this.mergedProps,
            ref: 'wrappedInstance'
          })
        } else {
          // this.mergedProps  是最終給組件的屬性
          this.renderedElement = createElement(WrappedComponent,
            this.mergedProps
          )
        }

        return this.renderedElement
      }
    }

    return hoistStatics(Connect, WrappedComponent)
  }
}

四 Vuex源碼分析

vuex是專門爲Vue提供的全局狀態管理框架,具體的概念和使用參見文檔:https://vuex.vuejs.org/zh-cn/。本文從源碼的角度進行分析。

完整Vuex註釋參見倉庫:https://github.com/yylgit/vuex

index.js是入口文件,導出了6個關鍵方法。

import { Store, install } from './store'
import { mapState, mapMutations, mapGetters, mapActions } from './helpers'

export default {
  Store, //建立store對象的方法
  install, //安裝vuex插件的方法
  version: '__VERSION__',
  mapState, //將store中的state映射到vue組件computed的方法
  mapMutations, //將store中的mutation映射到vue組件methods的方法
  mapGetters, //將store中的state映射到vue組件computed的方法
  mapActions //將store中的action映射到vue組件methods的方法
}

4.1 Vuex的裝載

首先講到install方法,咱們安裝vuex使用Vue.use方法,會將Vue傳遞給vuex的install方法並執行

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

可是若是在引入vuex以前已經將Vue掛在了window對象上的話,則不須要再調用Vue.use方法,相關源碼以下:

store.js

export function install (_Vue) {
  if (Vue) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}

// auto install in dist mode
//若是window上有Vue則自動安裝
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue)
}

install方法中調用了applyMixin方法,該方法在mixin.js文件中,其中若是Vue的版本大於等於2時,將vuexInit 函數mixin到init或者beforeCreate生命週期函數中,1.x版本時,經過重寫Vue.prototype._init方法,將vuexInit函數放在_init的options中,_init方法在Vue的構造函數中會調用。因此在每個vue實例建立的時候都會調用vuexInit方法。

mixin.js

export default function (Vue) {
  const version = Number(Vue.version.split('.')[0])
  /**
   * 若是是2.x.x以上版本,可使用 hook 的形式進行注入,或使用封裝並替換Vue對象原型的_init方法,實現注入。
   */
  //Vue2 經過Vue組件的init方法或者beforeCreate方法
  if (version >= 2) {
    const usesInit = Vue.config._lifecycleHooks.indexOf('init') > -1
    Vue.mixin(usesInit ? { init: vuexInit } : { beforeCreate: vuexInit })
  } else {
    // override init and inject vuex init procedure
    // for 1.x backwards compatibility.
    //由於Vue的構造函數會調用_init方法
    const _init = Vue.prototype._init
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
      _init.call(this, options)
    }
  }

再看一下vuexInit方法,將store對象掛在每個vue實例的$store屬性上。

/**
   * Vuex init hook, injected into each instances init hooks list.
   * 初始化Vue根組件時傳入的store設置到this.$store屬性上,
   * 子組件從其父組件引用$store屬性,層層嵌套進行設置。
   * 在任意組件中執行 this.$store 都能找到裝載的那個store對象
   */

  function vuexInit () {
    const options = this.$options
    // store injection
    if (options.store) {
      //根組件
      this.$store = options.store
      //子組件
    } else if (options.parent && options.parent.$store) {
      this.$store = options.parent.$store
    }
  }

4.2 Store構造函數

安裝了Vuex以後,咱們將利用Store方法建立store對象,示例以下:

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

那麼下面就看看Store方法的構造函數中都作了什麼事情。

  • 聲明變量

    • this._committing 標識是否利用commit改變state

    • this._actions 存放全部模塊的action,其中key值已經加上命名空間

    • this._mutations 存放全部模塊的mutation,其中key值已經加上命名空間

    • this._wrappedGetters 存放全部模塊的getter,其中key值已經加上命名空間

    • this._modules 存放模塊樹

    • this._modulesNamespaceMap 存放有命名空間的模塊與命名空間之間的map

    • this._subscribers 存放訂閱state變化的函數

    • this._watcherVM 提供一個VM用於監聽state和getter的變化

    • this.dispatch 綁定this爲store的dispatch

    • this.commit 綁定this爲store的dispatch

    • state 用於存放全部模塊state樹的rootState

    • installModule 安裝模塊的方法

    • resetStoreVM 設置store._vm

    • plugins.forEach安裝插件

具體代碼以下:

constructor (options = {}) {
    //已經執行安裝函數進行裝載;
    //支持Promise語法
    //必須用new操做符
    if (process.env.NODE_ENV !== 'production') {
      assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
      assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
      assert(this instanceof Store, `Store must be called with the new operator.`)
    }

    const {
      plugins = [],
      strict = false
    } = options

    let {
      state = {}
    } = options
    if (typeof state === 'function') {
      state = state()
    }

    // store internal state
    //是否正在commit
    this._committing = false
    this._actions = Object.create(null)
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    this._modules = new ModuleCollection(options) // Vuex支持store分模塊傳入,存儲分析後的modules
    this._modulesNamespaceMap = Object.create(null)  // 命名空間與對應模塊的map
    this._subscribers = []   // 訂閱函數集合,Vuex提供了subscribe功能
    this._watcherVM = new Vue()  // Vue組件用於watch監視變化

    // bind commit and dispatch to self
    const store = this
    const { dispatch, commit } = this

    //封裝替換原型中的dispatch和commit方法,將this指向當前store對象,當該方法不是使用store調用時,this仍然指向store
    this.dispatch = function boundDispatch (type, payload) {
      return dispatch.call(store, type, payload)
    }
    this.commit = function boundCommit (type, payload, options) {
      return commit.call(store, type, payload, options)
    }

    // strict mode
    this.strict = strict

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    // state對象,通過installModule以後已經成了rootState
    resetStoreVM(this, state)

    // apply plugins
    plugins.concat(devtoolPlugin).forEach(plugin => plugin(this))
  }

4.3 Store的模塊樹和命名空間

在構造函數中咱們能夠看到,開始處理options參數的是這行代碼

this._modules = new ModuleCollection(options)

它就是根據咱們傳入的store參數去構造store模塊樹。

關於store的模塊化和命名空間參見文檔:https://vuex.vuejs.org/zh-cn/...

歸納起來包括如下幾點:

  • Vuex的store能夠分模塊,模塊能夠添加命名空間,添加了命名空間的模塊有局部的上下文。

  • 傳給mutation的是局部的state。

  • 傳給action的是局部和全局的state和getter。

  • 傳給getter的是局部和全局的state和getter。

  • 默認的commit和dispatch都是分發局部的mutation和action。

  • 若須要在全局命名空間內分發 action 或提交 mutation,將 { root: true } 做爲第三參數傳給 dispatch 或 commit 便可

對於模塊樹的構造,咱們首先須要看一下模塊節點的構造函數module/module.js

  • this._rawModule 存放原始的模塊對象

  • this.state 指向this._rawModule.state或者是this._rawModule.state()

  • this._children 存放子模塊

  • addChild,removeChild,getChild 添加,刪除和獲取子模塊

  • forEachChild,forEachGetter,forEachAction,forEachMutation分別提供
    遍歷這幾種元素的方法

  • update 提供更新rawModule的方法

  • namespaced 判斷模塊是否有命名空間

import { forEachValue } from '../util'

export default class Module {
  constructor (rawModule, runtime) {
    this.runtime = runtime
    //存放子模塊
    this._children = Object.create(null)
    //存放原始的模塊對象
    this._rawModule = rawModule
    const rawState = rawModule.state
    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
  }
    //判斷模塊是否有命名空間
  get namespaced () {
    return !!this._rawModule.namespaced
  }

  addChild (key, module) {
    this._children[key] = module
  }

  removeChild (key) {
    delete this._children[key]
  }

  getChild (key) {
    return this._children[key]
  }

  update (rawModule) {
    this._rawModule.namespaced = rawModule.namespaced
    if (rawModule.actions) {
      this._rawModule.actions = rawModule.actions
    }
    if (rawModule.mutations) {
      this._rawModule.mutations = rawModule.mutations
    }
    if (rawModule.getters) {
      this._rawModule.getters = rawModule.getters
    }
  }

  forEachChild (fn) {
    forEachValue(this._children, fn)
  }

  forEachGetter (fn) {
    if (this._rawModule.getters) {
      forEachValue(this._rawModule.getters, fn)
    }
  }

  forEachAction (fn) {
    if (this._rawModule.actions) {
      forEachValue(this._rawModule.actions, fn)
    }
  }

  forEachMutation (fn) {
    if (this._rawModule.mutations) {
      forEachValue(this._rawModule.mutations, fn)
    }
  }
}

再看一下模塊樹的構造函數module/module-collection.js

  • this.root,指向模塊樹的根模塊

  • register,根據path和參數,構造模塊,而且根據path掛載到root指向的模塊樹上,而後遍歷參數的modules對象,遞歸調用register。

  • unregister,根據path從模塊樹上卸載模塊

  • update,遞歸更新整個模塊樹

  • get 根據path從模塊樹上獲取module,例如path爲['a'],獲取到a的module

  • getNamespace 獲取模塊樹上某一個模塊的命名空間

注意:
模塊的命名空間只與模塊設置的namespaced屬性有關。沒有設置namespaced屬性的模塊它的命名空間仍是全局的。

import Module from './module'
import { assert, forEachValue } from '../util'

export default class ModuleCollection {
  constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    this.register([], rawRootModule, false)
  }

  //根據path從模塊樹上獲取module,例如path爲['a'],獲取到a的module
  get (path) {
    return path.reduce((module, key) => {
      return module.getChild(key)
    }, this.root)
  }
  //獲取模塊樹上某一個模塊的命名空間,若是全部模塊的namespaced都爲true,那麼獲得的命名空間就和path相同
  getNamespace (path) {
    let module = this.root
    return path.reduce((namespace, key) => {
      module = module.getChild(key)
      return namespace + (module.namespaced ? key + '/' : '')
    }, '')
  }
  //遞歸更新整個模塊樹
  update (rawRootModule) {
    update([], this.root, rawRootModule)
  }


  /**
   *根據path和參數,構造模塊,而且根據path掛載到root
   *指向的模塊樹上,而後遍參數的modules對象,遞歸調用register。
   */
  register (path, rawModule, runtime = true) {
    if (process.env.NODE_ENV !== 'production') {
      assertRawModule(path, rawModule)
    }

    const newModule = new Module(rawModule, runtime)
    //根module
    if (path.length === 0) {
      this.root = newModule
    } else {
      //掛到父級module
      const parent = this.get(path.slice(0, -1))
      parent.addChild(path[path.length - 1], newModule)
    }

    // register nested modules
    //子module
    if (rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }
  //根據path從模塊樹上卸載模塊
  unregister (path) {
    const parent = this.get(path.slice(0, -1))
    const key = path[path.length - 1]
    if (!parent.getChild(key).runtime) return

    parent.removeChild(key)
  }
}

function update (path, targetModule, newModule) {
  if (process.env.NODE_ENV !== 'production') {
    assertRawModule(path, newModule)
  }

  // update target module
  targetModule.update(newModule)

  // update nested modules
  if (newModule.modules) {
    for (const key in newModule.modules) {
      if (!targetModule.getChild(key)) {
        if (process.env.NODE_ENV !== 'production') {
          console.warn(
            `[vuex] trying to add a new module '${key}' on hot reloading, ` +
            'manual reload is needed'
          )
        }
        return
      }
      update(
        path.concat(key),
        targetModule.getChild(key),
        newModule.modules[key]
      )
    }
  }
}

function assertRawModule (path, rawModule) {
  ['getters', 'actions', 'mutations'].forEach(key => {
    if (!rawModule[key]) return

    forEachValue(rawModule[key], (value, type) => {
      assert(
        typeof value === 'function',
        makeAssertionMessage(path, key, type, value)
      )
    })
  })
}

function makeAssertionMessage (path, key, type, value) {
  let buf = `${key} should be function but "${key}.${type}"`
  if (path.length > 0) {
    buf += ` in module "${path.join('.')}"`
  }
  buf += ` is ${JSON.stringify(value)}.`

  return buf
}

4.4 模塊的安裝

上一節中,講到了將構造的模塊樹存到了this._modules中,接下來開始遍歷模塊樹進行安裝

installModule(this, state, [], this._modules.root)

installModule方法作了以下幾件事情:

  • 若是模塊有命名空間,將對應關係存入store._modulesNamespaceMap中

  • 調用store._withCommit設置模塊的state到state樹上

  • 建立模塊的局部上下文 local

  • 循環註冊模塊的mutation、action和getter到 store._mutations、store._actions和store._wrappedGetters中

  • 遍歷模塊的子模塊遞歸安裝

具體代碼以下:

//安裝模塊
function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  const namespace = store._modules.getNamespace(path)

  // register in namespace map
  if (module.namespaced) {
    //若是這個模塊是有命名空間的,則將命名空間與模塊之間的關係存入_modulesNamespaceMap
    store._modulesNamespaceMap[namespace] = module
  }

  //非根組件而且非熱更新,熱更新是用新的模塊替換原來的模塊
  if (!isRoot && !hot) {
    //根據path獲取上一級state對象
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    //把模塊的state設置在rootState樹上
    store._withCommit(() => {
      Vue.set(parentState, moduleName, module.state)
    })
  }

  //建立命名空間下的context對象,包括state,getter,dispatch,commit
  const local = module.context = makeLocalContext(store, namespace, path)

  //註冊模塊的mutation,action和getter到store中
  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })

  module.forEachAction((action, key) => {
    const namespacedType = namespace + key
    registerAction(store, namespacedType, action, local)
  })

  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
  })

  //遞歸安裝子模塊
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

模塊的命名空間體如今了模塊註冊的的各個部分,首先是局部上下文的建立

const local = module.context = makeLocalContext(store, namespace, path)

上下文包括了四個部分

  • dispatch方法,若是命名空間是空字符串,則直接返回store.dispatch,若是有命名空間,而且調用dispath的時候第三個參數options.root!=true的狀況下,就會在調用store.dispatch的時候type加上命名空間,這樣就只調用命名空間下的action。

  • commit方法,與dispatch方法同理

  • getters對象,從從store.getters中篩選命名空間下的getters

  • state對象,根據path從store.state中找模塊對應的state

若是沒有命名空間的話,那麼全局的上下文就是store中的這四個元素。

具體makeLocalContext方法以下:

/**
 * make localized dispatch, commit, getters and state
 * if there is no namespace, just use root ones
 */
//根據命名空間來生成局部的上下文,包括type加上namespace的dispatch,commit,還有根據namespace獲取的局部state和getter
function makeLocalContext (store, namespace, path) {
  const noNamespace = namespace === ''

  const local = {
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      //在!options.root的狀況下type添加命名空間
      if (!options || !options.root) {
        //在type前面加上namespace,只觸發該namespace的actions
        type = namespace + type
        if (process.env.NODE_ENV !== 'production' && !store._actions[type]) {
          console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
          return
        }
      }

      return store.dispatch(type, payload)
    },

    commit: noNamespace ? store.commit : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      //在!options.root的狀況下type添加命名空間
      if (!options || !options.root) {
         //在type前面加上namespace,只觸發該namespace的mutation
        type = namespace + type
        if (process.env.NODE_ENV !== 'production' && !store._mutations[type]) {
          console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
          return
        }
      }

      store.commit(type, payload, options)
    }
  }

  // getters and state object must be gotten lazily
  // because they will be changed by vm update
  Object.defineProperties(local, {
    getters: {
      get: noNamespace
        ? () => store.getters
        : () => makeLocalGetters(store, namespace)
    },
    //<2>local的state仍是從store中取的state
    state: {
      get: () => getNestedState(store.state, path)
    }
  })

  return local
}

其中還用到了以下方法:

makeLocalGetters方法

//生成命名空間下的getter,從store的getter中篩選前綴是namespace的屬性
function makeLocalGetters (store, namespace) {
  const gettersProxy = {}

  const splitPos = namespace.length
  Object.keys(store.getters).forEach(type => {
    // skip if the target getter is not match this namespace
    if (type.slice(0, splitPos) !== namespace) return

    // extract local getter type
    const localType = type.slice(splitPos)

    // Add a port to the getters proxy.
    // Define as getter property because
    // we do not want to evaluate the getters in this time.
    Object.defineProperty(gettersProxy, localType, {
      get: () => store.getters[type],
      enumerable: true
    })
  })

  return gettersProxy
}

unifyObjectStyle方法用於dispatch和commit方法參數的適配處理

//參數的適配處理 
//能夠只傳一個對象參數,對象中有type,對象自己是payload,第二個參數是options
function unifyObjectStyle (type, payload, options) {
  if (isObject(type) && type.type) {
    options = payload
    payload = type
    type = type.type
  }

  if (process.env.NODE_ENV !== 'production') {
    assert(typeof type === 'string', `Expects string as the type, but found ${typeof type}.`)
  }

  return { type, payload, options }
}

getNestedState方法

//根據path獲取state狀態,註冊state樹的時候用到
function getNestedState (state, path) {
  return path.length
    ? path.reduce((state, key) => state[key], state)
    : state
}

建立了上下文之後,就開始註冊mutation、action、和getter。

註冊mutaion的方法,能夠看到

  • store._mutations[type]爲數組,也就是說能夠有多個key值相同的mutation

  • 只傳給mutation的是local.state,即不建議利用mutation操做命名空間以外的state

  • 咱們是直接在咱們寫的mutation中改變state,而不須要像redux中寫reducer那樣要返回一個新的對象,纔可以觸發訂閱state變化的事件

  • store.state是get,不能直接修改,而local.state是從state對象上找的指針,因此能夠向直接操做Vue中定義的data同樣直接操做改變,而能觸發響應。

//註冊mutation,將mutation存入store._mutations,傳入的type爲模塊namespace+mutation的key值
//store._mutations[type]爲數組,也就是說能夠有多個key值相同的mutation
function registerMutation (store, type, handler, local) {
  const entry = store._mutations[type] || (store._mutations[type] = [])
  entry.push(function wrappedMutationHandler (payload) {
    //這裏的handler是咱們本身寫的mutation函數,
    //最終調用mutation的時候傳入的是局部的state。
    //這是最終改變state的地方
    //我猜測沒有傳入全局state的緣由是不想讓咱們利用局部的mutation改變全局的state
    //而把全局的state傳入了action,這樣就能夠在action中拿到全局的state做爲payload
    //傳入mutation
    handler(local.state, payload)
  })
}

註冊action的方法

  • 能夠看到將上下文中的四項都傳給了它,並且還傳了store的getters和state,因此在action中能夠調用store中的任何state和getters來觸發該命名空間下和全局的action和mutation,複雜的組合邏輯均可以寫到action函數中。

  • 還能夠看到store._actions中的函數返回的確定都是Promise

//註冊action到store._actions,傳入的type爲模塊的namespace+action的key值
//store._actions[type]爲數組,也就是說能夠有多個key值相同的action
function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload, cb) {
    //這裏的handler是咱們本身寫的action函數
    //能夠看到傳入了局部的dispatch,commit,getter,state,還有全局的getter和state
    let res = handler({
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload, cb)
    //若是action返回的結果不是Promise,也會包裝成Promise,因此最後action返回的結果是Promsie
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}

註冊getter的方法

  • 註冊getter的時候若是重名之前面的爲準

  • getter也均可以利用全局的state和getter來組合

//註冊getter,一樣type是模塊的namespace+getter的key值
function registerGetter (store, type, rawGetter, local) {
  //getter不是數組,是惟一的函數,action和mutation是數組
  //若是已經有了則return,說明註冊getter的時候若是重名之前面的爲準
  if (store._wrappedGetters[type]) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(`[vuex] duplicate getter key: ${type}`)
    }
    return
  }
  store._wrappedGetters[type] = function wrappedGetter (store) {
    //傳給getter的四個參數
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters
    )
  }
}

至此,模塊的安裝就告一段落,經歷了installModule以後,Store的_actions,_mutations,_wrappedGetters,還有內部的state就都擁有了內容。

4.5 設置Store的VM對象

在構造函數中調用以下:

resetStoreVM(this, state)

resetStoreVM方法

  • 設置store._vm爲VM,其中將內部變量state做爲data,將store._wrappedGetters做爲計算屬性,利用了VM的雙向綁定和計算屬性的緩存

  • 設置store.getters,指向store._vm的計算屬性,利用它的緩存

  • 清空舊VM的數據並銷燬

function resetStoreVM (store, state, hot) {
  const oldVm = store._vm

  // bind store public getters
  store.getters = {}
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  forEachValue(wrappedGetters, (fn, key) => {
    // use computed to leverage its lazy-caching mechanism
    //將getter存到computed對象中,而後給_vm做爲計算屬性,利用了算屬性的緩存機制
    computed[key] = () => fn(store)
    //設置store的getters,從_vm中取,也能夠直接get: () => fn(store)
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true // for local getters
    })
  })

  // use a Vue instance to store the state tree
  // suppress warnings just in case the user has added
  // some funky global mixins
  const silent = Vue.config.silent
  Vue.config.silent = true
  //利用install module以後獲得的rootState和store._wrappedGetters獲得的計算屬性
  //建立Vue對象做爲store._vm
  store._vm = new Vue({
    data: {
      $$state: state   //this.state
    },
    computed
  })
  Vue.config.silent = silent

  // enable strict mode for new vm
  if (store.strict) {
    enableStrictMode(store)
  }

  if (oldVm) {
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        oldVm._data.$$state = null
      })
    }
    Vue.nextTick(() => oldVm.$destroy())
  }
}

最終對外的store.state是經過getter來訪問store._vm._data.$$state,實現了只讀的效果。

//取得this._vm._data.$$state
  get state () {
    return this._vm._data.$$state
  }
 
  //不能直接給state賦值
  set state (v) {
    if (process.env.NODE_ENV !== 'production') {
      assert(false, `Use store.replaceState() to explicit replace store state.`)
    }
  }

4.6 commit and dispatch方法

commit方法是store對象對外提供的執行mutation的方法

  • 根據typethis._mutations中找到mutation並依次執行

  • 遍歷執行this._subscribers觸發訂閱state變化的函數

//對外提供的觸發mutation的方法
  commit (_type, _payload, _options) {
    // check object-style commit
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)

    const mutation = { type, payload }
    const entry = this._mutations[type]
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown mutation type: ${type}`)
      }
      return
    }
    //執行註冊的mutation,
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    //觸發訂閱者
    this._subscribers.forEach(sub => sub(mutation, this.state))

    if (
      process.env.NODE_ENV !== 'production' &&
      options && options.silent
    ) {
      console.warn(
        `[vuex] mutation type: ${type}. Silent option has been removed. ` +
        'Use the filter functionality in the vue-devtools'
      )
    }
  }

在執行mutation改變state的時候調用了_withCommit方法,它的做用是執行改變state的時候,保證store._committing === true。在resetStoreVM時,若是是設置了嚴格模式store.strict == true,則調用enableStrictMode方法,利用store._vm
watch方法,監聽state的變化,若是變化,則判斷store._committing === true,若是不是則發出警告不要利用mutation以外的方法改變state。

/**
* 
* 保存執行時的committing狀態將當前狀態設置爲true後進行本次提交操做,待操做完畢後,將committing狀態還原爲以前的狀態
*/
 _withCommit (fn) {
   // 保存以前的提交狀態
   const committing = this._committing
    // 進行本次提交,若不設置爲true,直接修改state,strict模式下,Vuex將會產生非法修改state的警告
   this._committing = true
   // 執行state的修改操做
   fn()
   // 修改完成,還本來次修改以前的狀態
   this._committing = committing
 }
}
function enableStrictMode (store) {
 store._vm.$watch(function () { return this._data.$$state }, () => {
   if (process.env.NODE_ENV !== 'production') {
     assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
   }
 }, { deep: true, sync: true })
}

dispatch方法是store對象對外提供的執行action的方法,返回值是promise

dispatch (_type, _payload) {
    // check object-style dispatch
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const entry = this._actions[type]
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown action type: ${type}`)
      }
      return
    }
    //逐個執行action,返回promise
    return entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)
  }

4.7 訂閱state變化的方法

store提供了兩個訂閱state變化的方法,一個是subscribe,一個是watch

subscribe方法將訂閱函數放在store._subscribers中,用於監聽state的變化,實際上是監聽commit方法的執行,在上一節的commit代碼中能夠看到,只要執行commit方法就觸發store._subscribers中函數的執行。

//訂閱state變化的方法
  subscribe (fn) {
    const subs = this._subscribers
    if (subs.indexOf(fn) < 0) {
      subs.push(fn)
    }
    return () => {
      const i = subs.indexOf(fn)
      if (i > -1) {
        subs.splice(i, 1)
      }
    }
  }

watch方法用來訂閱咱們對於store.statestore.getters自定義屬性的變化,利用了store._watcherVM.$watch方法

watch (getter, cb, options) {
    if (process.env.NODE_ENV !== 'production') {
      assert(typeof getter === 'function', `store.watch only accepts a function.`)
    }
    return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
  }

4.8 更新store的方法

replaceState替換store.state的方法

//替換全局的state
  replaceState (state) {
    this._withCommit(() => {
      this._vm._data.$$state = state
    })
  }

registerModule在store中註冊新模塊的方法,適用於多業務線之間的配合,每個業務線將自身的模塊註冊到sore中。

registerModule (path, rawModule) {
    if (typeof path === 'string') path = [path]

    if (process.env.NODE_ENV !== 'production') {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
      assert(path.length > 0, 'cannot register the root module by using registerModule.')
    }

    //在模塊樹上進行註冊
    this._modules.register(path, rawModule)
    //安裝該模塊
    installModule(this, this.state, path, this._modules.get(path))
    // reset store to update getters...
    //更新_vm,主要是更新VM的計算屬性和store.getters
    resetStoreVM(this, this.state)
  }

resetStore重置store的方法,除了state不變之外,從新安裝模塊和重設store._vm

//重置store
function resetStore (store, hot) {
  store._actions = Object.create(null)
  store._mutations = Object.create(null)
  store._wrappedGetters = Object.create(null)
  store._modulesNamespaceMap = Object.create(null)
  const state = store.state
  // init all modules
  //state沒有變,installModule的第五個參數爲true,不會重置state
  installModule(store, state, [], store._modules.root, true)
  // reset vm
  resetStoreVM(store, state, hot)
}

hotUpdate熱更新模塊的方法,調用模塊樹的update方法,更新模塊樹,而後resetStore,不會更新state。

//熱更新store
  hotUpdate (newOptions) {
    this._modules.update(newOptions)
    resetStore(this, true)
  }

4.9 helpers.js

Vuex的入口文件除了導出Storeinstall方法以外,還導出了四個幫助咱們在Vue中使用Vuex的方法mapState,mapMutations,mapGetters,mapActions,他們都放在helpers.js文件中,因此咱們要分析下這個文件。

  • getModuleByNamespace利用存儲在store._modulesNamespaceMap中namespace與module的關係,來獲取module。

//根據命名空間獲取模塊
function getModuleByNamespace (store, helper, namespace) {
  const module = store._modulesNamespaceMap[namespace]
  if (process.env.NODE_ENV !== 'production' && !module) {
    console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
  }
  return module
}
  • normalizeNamespace 是內部幫助函數,規範命名空間的函數,兼容不傳namespace的狀況,若是不傳則是空字符串

//兼容沒有命名空間的函數
function normalizeNamespace (fn) {
  return (namespace, map) => {
    if (typeof namespace !== 'string') {
      map = namespace
      namespace = ''
    } else if (namespace.charAt(namespace.length - 1) !== '/') {
      namespace += '/'
    }
    return fn(namespace, map)
  }
}
  • normalizeMap是內部幫助函數,規範map的函數,能夠接受數組或者對象,最後返回的對象是{key,val}形式

function normalizeMap (map) {
  return Array.isArray(map)
    ? map.map(key => ({ key, val: key }))
    : Object.keys(map).map(key => ({ key, val: map[key] }))
}
  • mapState函數,先調用normalizeNamespace函數規範命名空間參數,而後規範化傳入的states對象,若是val是字符串則直接返回state[val],若是val是函數,則傳入state和getter返回函數執行結果。

export const mapState = normalizeNamespace((namespace, states) => {
  const res = {}
  normalizeMap(states).forEach(({ key, val }) => {
    res[key] = function mappedState () {
      let state = this.$store.state
      let getters = this.$store.getters
      if (namespace) {
        const module = getModuleByNamespace(this.$store, 'mapState', namespace)
        if (!module) {
          return
        }
        state = module.context.state
        getters = module.context.getters
      }
      return typeof val === 'function'
        ? val.call(this, state, getters)
        : state[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  return res
})
  • mapGetters函數,同理是先規範化,而後把key值添加上namespacethis.$store.getters中取。

export const mapGetters = normalizeNamespace((namespace, getters) => {
  const res = {}
  normalizeMap(getters).forEach(({ key, val }) => {
    val = namespace + val
    res[key] = function mappedGetter () {
      if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {
        return
      }
      if (process.env.NODE_ENV !== 'production' && !(val in this.$store.getters)) {
        console.error(`[vuex] unknown getter: ${val}`)
        return
      }
      return this.$store.getters[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  return res
})
  • mapMutations函數,同理先規範化,key值添加namespace,而後調用commit函數,觸發mutation。

export const mapMutations = normalizeNamespace((namespace, mutations) => {
  const res = {}
  normalizeMap(mutations).forEach(({ key, val }) => {
    val = namespace + val
    res[key] = function mappedMutation (...args) {
      if (namespace && !getModuleByNamespace(this.$store, 'mapMutations', namespace)) {
        return
      }
      //調用commit方法執行mutation
      return this.$store.commit.apply(this.$store, [val].concat(args))
    }
  })
  return res
})
  • mapActions函數,同理先規範化,key值添加namespace,而後調用dispatch函數,觸發action。

export const mapActions = normalizeNamespace((namespace, actions) => {
  const res = {}
  normalizeMap(actions).forEach(({ key, val }) => {
    val = namespace + val
    res[key] = function mappedAction (...args) {
      if (namespace && !getModuleByNamespace(this.$store, 'mapActions', namespace)) {
        return
      }
      //調用dispatch方法執行action
      return this.$store.dispatch.apply(this.$store, [val].concat(args))
    }
  })
  return res
})

5 Redux與Vuex的對比

1
Redux改變state的方法是在reducer中返回新的state對象,
Vuex是在mutation中直接改變state

2
state變化觸發視圖的更新,在Redux中是須要本身去監聽,而後從新setState,
在Vuex中是直接利用了VM的數據響應

3 Vuex將改變state的方法進行了細分,分紅mutation和action,更利於寫複雜的邏輯,還直接處理了異步的狀況,而Redux中須要在中間件中處理

相關文章
相關標籤/搜索