咱們都知道在用 vue 的時候,簡單的父子通訊和 EventBus 已經不能知足咱們的要求了,嵌套層級過多和難以追蹤改變是兩個較爲主要的問題😵,這個時候能夠用 vuex 來解決,想必你們都用過,因此今天跟你們分享的是 vuex 的簡單實現,真的是超簡單,就幾行代碼(文章結尾有連接),帶你領略 vuex 的精髓,而且在最後會有幾個問題答疑(好比時光穿梭、本地持久化等)幫你們鞏固一下 vuex。javascript
vuex 是基於 vue 的狀態管理工具,通俗點講講就是變量共享,你要知道 this.$store
本質是個對象,你們能夠看下下面這張圖,看看 this.$store
究竟是個啥(只看有標號的行便可)👇: css
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex); // 這是插件固定寫法,沒有什麼爲何,官網寫的很清楚,這樣寫 vue 會自動調用插件的 install 方法
const store = new Vuex.Store({
state: {...},
mutations: {...},
actions: {...},
getters: {...},
})
new Vue({
el: '#app',
store
})
複製代碼
很顯然,Store 裏面大體就傳入四個參數,目前咱們也只須要這些參數就夠了,四個參數又能夠分爲兩類,一類是獲取數據,一類是修改數據,就像下面這張圖畫的同樣👇: html
$store.state
和
$store.getters
,組件要修改數據能夠經過
$store.actions
和
$store.mutations
(固然最終都是 mutations,這也使得便於追蹤狀態的改變),如此一來,造成了閉環,也符合咱們所說的單向數據流的思想,只有一個地方能改數據,不能遍地開花,否則就亂套了。另外 store 也是個單例模式的例子,全部的組件都共用一個全局的 store,每一個組件的
$store
都是同一個東西,這點也很好理解。
有了上面這些概念,接下來咱們就簡單用幾行代碼來簡單實現一下吧。首先先寫個簡單的小框架,就像下面這樣(官網上有說明插件的編寫方式):vue
<script src="https://cdn.bootcss.com/vue/2.6.11/vue.js"></script>
<script> const Vuex = {} Vuex.install = function (Vue) { // 這個 Vue 是 vue 提供給咱們的 console.log('install 方法開始執行') } Vue.use(Vuex) </script>
複製代碼
ok,而後咱們在 install 裏面寫 vuex 的代碼便可,如今咱們只須要聲明一個 store 對象而且賦予 store 兩個屬性(這裏以 state 和 mutations 舉例),這個我用很簡單的代碼演示一下你們就清楚了👇:java
Vuex.install = function (Vue) {
console.log('install 方法開始執行')
const store = {} // 聲明一個對象
store.state = new Vue({ // 賦予 state 屬性,用來獲取值
data () {
return {
msg: '哈哈'
}
}
})
store.mutations = { // 賦予 mutations 屬性,用來修改值
SETMSG(value) {
store.state.msg = value
}
}
}
複製代碼
上面的代碼中要注意的是 state 實際上是利用了 vue 的響應式原理,使用了 new Vue()
,由此 state 就變成響應式的了,這是 vue 的特性。注意這裏單純的使用 Object.defineProperty
來定義 state 是不行的,是沒法更新視圖的,由於它並無被 vue 進行依賴收集,因此說 vuex 是強依賴於 vue 的。還有就是一開始咱們也要把 state 的數據寫全,否則後面添加的也是無響應更新的,你們若是有用到過 $set
應該能體會到爲何有時候數據變了,視圖沒更新那種感受😬。git
👌,如今咱們已經有了 store,接下來就是把它掛到每一個組件下面了,這裏咱們使用 Vue.mixin 的混入方式,代碼以下:github
Vue.mixin({ // 也是固定寫法:每一個組件都會執行下面這個生命週期
beforeCreate () {
this.$store = store // 因而每一個組件都會有 this.$store,而且都指向同一個 store
}
})
複製代碼
固然用 Vue.prototype.$store = store
也能達到一樣的效果,事實上,vue-router 也是一樣的方式咱們才能在每一個實例中用 this.$router 來調用。另外若是要說 mixin 和 prototype 這兩種掛載方式的區別,我就想到兩小點😯:vue-router
不知道你們還知道其餘緣由嗎,歡迎在下面留言。vuex
好了,至此,咱們大概就寫完了一個簡單的 demo,如今寫個例子來測試一下:api
let v1 = new Vue({
el: '#component1',
computed: {
data() {
return this.$store.state.msg
}
}
})
let v2 = new Vue({
el: '#component2',
methods: {
change() {
this.$store.mutations.SETMSG('這是 mutations 觸發的值')
}
}
})
console.log(v1, v2)
複製代碼
下面是測試的結果:
Vuex.install = function (Vue) {
...
store.commit = function(mutationName, value) {
store.mutations[mutationName] && store.mutations[mutationName](value)
}
...
}
...
let v2 = new Vue({
el: '#component2',
methods: {
change() { // 改一下調用方式,結果是同樣的
// this.$store.mutations.SETMSG('這是 mutations 觸發的值')
this.$store.commit('SETMSG', '這是 mutations 觸發的值')
}
}
})
複製代碼
最終效果是同樣的,這裏就不展現了。 深吸一口氣,目前爲止咱們已經實現超簡版的 vuex,接下來是幾個問題答疑🤔。
這個東西其實和咱們平時寫的計算屬性一毛同樣,state 和 getters 的關係比如 data 和 computed,你們細品一下。
咱們知道 vuex 中修改 state 就一個通道,就是執行 commit,但其實你不經過 commit 也是能改的,那怎麼知道它是經過 commit 修改的呢?就是在執行 commit 的時候加個標誌位 _committing
,執行 commit 的時候將 _committing
設置爲 true,_committing
爲 true 才能修改 state,而其餘方式修改的 state 並不會修改 _committing
標誌位,這樣一來就能判斷是否是經過 commit 修改的。 若是你在 vuex 中打開了嚴格模式,任何非 mutation 更改都會拋出錯誤。
這一類輔助函數本質就是語法糖,這裏咱們以 mapState 舉例子,咱們回顧一下用法:
import {mapState} from 'vuex'
export default{
computed:{
...mapState(['msg','user'])
}
}
複製代碼
而後咱們在頁面中就能用 this.msg 訪問,其實調用的仍是 this.$store.state.msg,只不過寫起來簡單點。下面咱們看下怎麼簡單實現,很顯然,這裏 mapState 是一個函數,接收一個數組:
function mapState (list) {
let obj = {}
list.forEach(stateName => {
obj[stateName] = () => this.$store.state[stateName]
})
return obj
}
複製代碼
👏是的,就這麼點代碼,其餘 map 輔助函數也是也同樣的道理。
這是 devtoolPlugin 提供的功能,由於開發的時候全部 state 的改變都有記錄,「時光穿梭」的功能其實就是可以讓咱們回到或去到某一狀態,實際上就是將當前的 state 替換爲記錄中的某個 state,咱們看下 vuex 的 store 中就爲咱們提供了一個這樣的函數 replaceState(真的是直接替換😂),具體代碼以下👇:
replaceState (state) {
this._withCommit(() => { // 這裏面就是上面說到的 _commiting 標識
this._vm.state = state
})
}
複製代碼
下面是兩個個小截圖,但願可以幫助你理解:
若是硬要說 vuex 中最核心的一個點的話,那就是利用了 vue 的響應式,我想這是最爲主要的。最後但願本篇文章可以對你有所幫助,不知道寫的清不清楚😁,也祝你們百毒不侵,開開心心上班,回見👋。
ps: 最簡版 vuex 代碼地址、精裝版 vuex 代碼地址