Redux和Vuex都是全局狀態管理框架,我以爲他們的出現有四個緣由:html
第一,是SPA,若是不是SPA,多頁面之間也就不存在複雜的交互,每打開一個頁面表明新的生命週期,頁面之間的交互能夠利用window對象和URL參數,固然也存在一個頁面的組件之間有複雜的交互,可是Redux和Vuex確實是更適用於SPA的場景,頁面做爲組件,頁面之間能夠共同管理數據狀態,更像是一個應用,Redux和Vuex管理着這個應用的全部狀態。vue
第二,是組件化,我認爲組件化有兩種,第一種是與業務邏輯無關的,能夠複用的組件,第二種是業務邏輯的組件,提取出來是爲了代碼的可維護性,可讀性。組件化以後,一個頁面可能有幾個甚至十幾個組件組成,並且還可能出現幾層嵌套的狀況,這就產生了組件之間共同管理狀態的場景。react
第三,是複雜的交互邏輯,有了SPA有了組件化,再出現複雜的交互邏輯,須要多頁面多組件之間的數據共享。若是是同一頁面內,在沒有管理框架的時候咱們都是把共享狀態放在根組件,而後將利用屬性下傳狀態和管理方法,或者利用全局事件的方式。全局事件多了管理混亂,傳屬性的話,可能出現代碼重複,而且也是管理不規範。git
第四,是數據與視圖的綁定,數據驅動視圖的更新的方式,使得數據的管理相當重要,數據決定了視圖的展現,因此對於數據的管理須要一套規範。github
具體的使用和概念參見Redux中文文檔 http://cn.redux.js.org//index...。本文從源碼的角度進行分析。vuex
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。
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 } }
提供了組合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 } }
提供能夠插入中間件的方法,應用中間件的目的是包裝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 } } }
/** * 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)) }
//添加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。
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等必要的模塊,下面就繼續看一下相關的模塊。
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; }
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);
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的返回值
以上都是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是專門爲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的方法 }
首先講到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 } }
安裝了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)) }
在構造函數中咱們能夠看到,開始處理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 }
上一節中,講到了將構造的模塊樹存到了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
就都擁有了內容。
在構造函數中調用以下:
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.`) } }
commit
方法是store
對象對外提供的執行mutation的方法
根據type
從this._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) }
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.state
和store.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) }
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) }
Vuex的入口文件除了導出Store
和install
方法以外,還導出了四個幫助咱們在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值添加上namespace
從this.$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 })
1
Redux改變state的方法是在reducer中返回新的state對象,
Vuex是在mutation中直接改變state
2
state變化觸發視圖的更新,在Redux中是須要本身去監聽,而後從新setState,
在Vuex中是直接利用了VM的數據響應
3 Vuex將改變state的方法進行了細分,分紅mutation和action,更利於寫複雜的邏輯,還直接處理了異步的狀況,而Redux中須要在中間件中處理