Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式,它採用集中式存儲管理應用的所有組件的狀態,註意:使用前需要先載入vue文件才可以使用(在node.js下需要使用Vue.use(Vuex)來安裝vuex插件,在瀏覽器環境下直接載入即可,vuex會自行安裝) vuex的使用方法很簡單,首 ...
Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式,它採用集中式存儲管理應用的所有組件的狀態,註意:使用前需要先載入vue文件才可以使用(在node.js下需要使用Vue.use(Vuex)來安裝vuex插件,在瀏覽器環境下直接載入即可,vuex會自行安裝)
vuex的使用方法很簡單,首先調用new Vuex.Store(options)創建一個store實例即可,然後在創建vue實例時把這個store實例作為store屬性傳入即可,調用new Vuex.Store(options)創建一個vuex實例時可以傳入如下參數:
state 存儲的數據
getters 可以認為是store的計算屬性
mutations 這是更改Vuex的store里的數據的唯一方法,只能是同步方法(官網這樣寫的,其實不贊同這個說法,具體請看下麵)
actions 可以包含一些非同步操作,它提交的是mutation,而不是直接變更狀態。
modules 為了更方便的管理倉庫,我們把一個大的store拆成一些modules(子倉庫),整個modules是一個樹型結構
strict 是否開啟嚴格模式,無論何時發生了狀態變更且不是由mutation函數引起的,將會拋出錯誤,這能保證所有的狀態變更都能被調試工具跟蹤到。 ;預設為false
後面介紹每個api時單獨介紹用法,舉個慄子,如下:
writer by:大沙漠 QQ:22969969
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script src="https://unpkg.com/[email protected]/dist/vuex.js"></script>
</head>
<body>
<div id="app">
<p>{{message}}</p>
<p>{{reverseMessage}}</p>
<p>{{no}}</p>
<button @click="test1">mutation測試</button>
<button @click="test2">action測試</button>
</div>
<script>
const store = new Vuex.Store({
state:{message:'Hello World',no:123},
getters:{ //getters類似於Vue的計算屬性
reverseMessage:state=>{return state.message.split('').reverse().join('')},
increment:state=>{state.no++}
},
mutations:{ //mutation包含一些同步操作
increment(state,payload){state.no+=payload.no}
},
actions:{ //actions包含一些非同步操作
increment({commit},info){
setTimeout(function(){
commit('increment',info)
},500)
}
}
})
var app = new Vue({
el:"#app",
store,
computed:{
no:function(){return this.$store.state.no},
message:function(){return this.$store.state.message},
reverseMessage:function(){return this.$store.getters.reverseMessage}
},
methods:{
test1:function(){this.$store.commit('increment',{no:10})},
test2:function(){this.$store.dispatch('increment',{no:10})}
}
})
</script>
</body>
</html>
渲染如下:
我們點擊mutation測試這個按鈕123會這個數字會立馬遞增10,而點擊action測試這個按鈕,數字會延遲0.5秒,再遞增10,前者是mutation對應的同步操作,而後者是action對應的非同步操作
如果只是這樣顯式數據,感覺vuex沒有什麼用處,我們在瀏覽器里輸入store.state.message="Hello Vue"來直接修改state里的數據看看怎麼樣,如下:
修改後頁面里的內容立即就變化了,如下:
是不是很神奇,這裡只是一個組件引用了vuex,如果很多的組件都引用了同一個vuex實例,那麼只要狀態發生變化,對應的組件都會自動更新,這就是vuex的作用。
vuex官網說mutations是更改store里數據的唯一方法,這在邏輯上不嚴謹的,只有設置了strict為true,那麼說mutations是更改store里數據的唯一方法還可以接收,比如我們在控制台里直接修改store里的數據了,也沒報錯啥的。
vuex內部的實現原理很簡單,就是定義一個vue實例,把vuex.store里的state作為data屬性(不是根data,而是放到$$state這個屬性里,不過由於值是個對象,因此也是響應式的),getters作為計算屬性來實現的
源碼分析
我們先看看vuex插件導出了哪些符號,打開vuex的源文件,拉到最底部,如下:
var index = {
Store: Store, //初始化
install: install, //安裝方法
version: '3.1.0', //版本號
mapState: mapState, //State輔助函數
mapMutations: mapMutations, //Mutations輔助函數
mapGetters: mapGetters, //Getters輔助函數
mapActions: mapActions, //Actions輔助函數
createNamespacedHelpers: createNamespacedHelpers
};
可以看到Store就是初始化函數,install是安裝用的,version是版本號,其它幾個都是輔助函數,最後一個是和輔助函數的上下文綁定(也就是命名空間)相關,一般用不到。
我們先看看安裝流程,如下:
function install (_Vue) { //安裝Vuex
if (Vue && _Vue === Vue) { //如果Veue存在且等於參數_Vue,表示已經安裝過了,則報錯
{
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
);
}
return
}
Vue = _Vue; //將_Vue保存到局部變數Vue里
applyMixin(Vue); //調用applyMixin()進行初始化
}
安裝時最後會執行applyMixin函數,該函數如下:
function applyMixin (Vue) { //將Vuex混入到Vue裡面
var version = Number(Vue.version.split('.')[0]); //獲取主版本號
if (version >= 2) { //如果是Vue2.0及以上版
Vue.mixin({ beforeCreate: vuexInit }); //則執行Vue.mixin()方法,植入一個beforeCreate回調函數
} else {
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
var _init = Vue.prototype._init;
Vue.prototype._init = function (options) {
if ( options === void 0 ) options = {};
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit;
_init.call(this, options);
};
}
/**
* Vuex init hook, injected into each instances init hooks list.
*/
function vuexInit () { //Vuex的安裝方法
var options = this.$options;
// store injection
if (options.store) { //如果options.store存在,即初始化Vue實例時傳入了store實例
this.$store = typeof options.store === 'function' //則將store保存到大Vue的$store屬性上,如果store是個函數,則執行該函數
? options.store()
: options.store;
} else if (options.parent && options.parent.$store) { //如果options.store不存在,但是父實例存在$store(組件的情況下)
this.$store = options.parent.$store; //則設置this.$store為父實例的$store
}
}
}
這樣不管是根vue實例,還是組件,都可以通過this.$store來獲取到對應的$store實例了,安裝就是這樣子,下麵說一下整體流程
以上面的例子為例,當我們執行new Vuex.Store()創建一個Vuex.Store的實例時會執行到導出符號的Store函數,如下:
var Store = function Store (options) { //構造函數
var this$1 = this;
if ( options === void 0 ) options = {};
// Auto install if it is not done yet and `window` has `Vue`.
// To allow users to avoid auto-installation in some cases,
// this code should be placed here. See #731
if (!Vue && typeof window !== 'undefined' && window.Vue) { //如果局部變數Vue不存在且window.Vue存在,即已經引用了Vue,而且window.Vue不存在(還沒安裝)
install(window.Vue); //執行install()方法進行安裝 ;從這裡看出在瀏覽器環境下不需要執行Vue.use(vuex),在執行new Vuex.Store()會自己安裝
}
{
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.");
}
var plugins = options.plugins; if ( plugins === void 0 ) plugins = [];
var strict = options.strict; if ( strict === void 0 ) strict = false;
// store internal state
this._committing = false;
this._actions = Object.create(null);
this._actionSubscribers = [];
this._mutations = Object.create(null);
this._wrappedGetters = Object.create(null);
this._modules = new ModuleCollection(options); //初始化modules,ModuleCollection對象是收集所有模塊信息的
this._modulesNamespaceMap = Object.create(null);
this._subscribers = [];
this._watcherVM = new Vue();
// bind commit and dispatch to self
var store = this;
var ref = this;
var dispatch = ref.dispatch;
var commit = ref.commit;
this.dispatch = function boundDispatch (type, payload) { //重寫dispatch方法,將上下文設置為當前的this實例
return dispatch.call(store, type, payload)
};
this.commit = function boundCommit (type, payload, options) { //重寫commit方法,將上下文設置為當前的this實例
return commit.call(store, type, payload, options)
};
// strict mode
this.strict = strict;
var state = this._modules.root.state; //獲取根倉庫的state信息
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root); //安裝根模塊,該函數會遞歸調用的安裝子模塊,並收集它們的getters到this._wrappendGetters屬性上
// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state); //安裝vm,也就是這裡會創建一個vue實例,並把state、getter作為響應式對象
// apply plugins
plugins.forEach(function (plugin) { return plugin(this$1); }); //安裝插件
var useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools;
if (useDevtools) {
devtoolPlugin(this);
}
};
ModuleCollection模塊會收集根模塊和子模塊的的所有信息,例子里執行到這裡時對應的this._modules如下:
然後會調用執行到installModule()會安裝每個模塊,也就是把每個模塊的getters、mutations、actions進行一系列處理,如果還有子模塊(module屬性)則遞歸調用installModule依次處理每個子模塊,如下:
function installModule (store, rootState, path, module, hot) { //安裝模塊
var isRoot = !path.length; //當前是否為根Module
var namespace = store._modules.getNamespace(path); //獲取命名空間
// register in namespace map
if (module.namespaced) {
store._modulesNamespaceMap[namespace] = module;
}
// set state
if (!isRoot && !hot) {
var parentState = getNestedState(rootState, path.slice(0, -1));
var moduleName = path[path.length - 1];
store._withCommit(function () {
Vue.set(parentState, moduleName, module.state);
});
}
var local = module.context = makeLocalContext(store, namespace, path);
module.forEachMutation(function (mutation, key) { //遍歷module模塊的mutations對象
var namespacedType = namespace + key;
registerMutation(store, namespacedType, mutation, local); //調用registerMutation註冊mutation
});
module.forEachAction(function (action, key) { //遍歷module模塊的actions對象
var type = action.root ? key : namespace + key;
var handler = action.handler || action;
registerAction(store, type, handler, local); //調用registerAction註冊action
});
module.forEachGetter(function (getter, key) { //遍歷module模塊的getter對象
var namespacedType = namespace + key;
registerGetter(store, namespacedType, getter, local); //調用registerGetter註冊getter
});
module.forEachChild(function (child, key) { //如果有定義了module(存在子模塊的情況)
installModule(store, rootState, path.concat(key), child, hot); //則遞歸調用installModule
});
}
最後會執行resetStoreVM()函數,該函數內部會創建一個vue實例,這樣state和getters就是響應式數據了,如下:
function resetStoreVM (store, state, hot) { //重新存儲數據
var oldVm = store._vm;
// bind store public getters
store.getters = {};
var wrappedGetters = store._wrappedGetters; //獲取store的所有getter數組信息
var computed = {};
forEachValue(wrappedGetters, function (fn, key) { //遍歷wrappedGetters
// use computed to leverage its lazy-caching mechanism
computed[key] = function () { return fn(store); }; //將getter保存到computed裡面
Object.defineProperty(store.getters, key, {
get: function () { return 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
var silent = Vue.config.silent; //保存Vue.config.silent的配置
Vue.config.silent = true; //設置Vue.config.silent配置屬性為true(先關閉警告)
store._vm = new Vue({ //創建new Vue()實例把$$state和computed變成響應式的
data: {
$$state: state
},
computed: computed
});
Vue.config.silent = silent; //將Vue.config.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(function () {
oldVm._data.$$state = null;
});
}
Vue.nextTick(function () { return oldVm.$destroy(); });
}
}
這樣整個流程就跑完了,就是內部創建一個vue實例,利用vue的響應式做數據動態響應。