一、Vuex概述
引用官方文檔的話解釋什么是Vuex
Vuex 是一個專為 Vue.js 應(yīng)用程序開發(fā)的狀態(tài)管理模式 + 庫。它采用集中式存儲管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測的方式發(fā)生變化。
設(shè)想每個Vue組件都只需要關(guān)心自己的數(shù)據(jù)和數(shù)據(jù)處理邏輯,組件之間完全獨立,沒有共享數(shù)據(jù)的需求,那么web應(yīng)用會非常簡單。
但是實際情況是,一個web應(yīng)用中不同組件常常會有數(shù)據(jù)共享的需求。例如文檔類型的應(yīng)用,編輯的內(nèi)容和大綱之間要共享數(shù)據(jù),大綱列表組件需要根據(jù)編輯的內(nèi)容生成文檔大綱、電商類型的應(yīng)用,商品展示列表需要根據(jù)用戶選擇的分類展示相應(yīng)的商品,那么商品列表組件需要知道分類選項組件中用戶選擇的是哪個類別。
我們知道Vue父組件可以和子組件之間通過props、事件、ref引用來進行通信,但是這種組件之間的通信方式在規(guī)模較大的應(yīng)用中會有些力不從心。因此我們更傾向于將不同組件通用的數(shù)據(jù)提取出來統(tǒng)一管理。
我們知道Vue組件根據(jù)數(shù)據(jù)渲染視圖,不同的數(shù)據(jù)對應(yīng)不同的視圖。我們也可以理解為web應(yīng)用處于不同的“狀態(tài)”,每個狀態(tài)對應(yīng)數(shù)據(jù)的一組取值。因此我們將數(shù)據(jù)抽出來統(tǒng)一管理也可以稱為“狀態(tài)管理”。
總之,Vuex就是專為Vue.js開發(fā)的狀態(tài)管理工具,用于解決組件之間數(shù)據(jù)共享的需求。
狀態(tài)管理工具都需要包含哪些要素?
- 初始狀態(tài)
- 改變狀態(tài)
- 監(jiān)聽狀態(tài)的改變
首先我們看下Vuex都有哪些API,然后說明這些API的作用。
- state
- getters
- mutations
- actions
- module
- 輔助函數(shù):mapState、mapGetters、mapMutations、mapActions
- createStore
其中state和getters用來保存狀態(tài);mutations和actions用來改變狀態(tài);監(jiān)聽狀態(tài)用的是Vue組件中的computed屬性;module是用來組織整個應(yīng)用的狀態(tài)管理代碼,使?fàn)顟B(tài)劃分模塊,更易于管理;輔助函數(shù)用來在監(jiān)聽狀態(tài)時候簡化代碼,createStore則用來創(chuàng)建狀態(tài)管理對象。
Vuex狀態(tài)管理就是創(chuàng)建一個對象(store),這個對象上面保存了應(yīng)用中大部分?jǐn)?shù)據(jù),組件可以通過store獲取數(shù)據(jù),也可以改變數(shù)據(jù)的值。上面說的這些能力一個普通的js對象也可以做到。store和普通的對象的區(qū)別是:
- Vuex 的狀態(tài)存儲是響應(yīng)式的。當(dāng) Vue 組件從 store 中讀取狀態(tài)的時候,若 store 中的狀態(tài)發(fā)生變化,那么相應(yīng)的組件也會相應(yīng)地得到高效更新。
- 你不能直接改變 store 中的狀態(tài)。改變 store 中的狀態(tài)的唯一途徑就是顯式地提交 (commit) mutation。這樣使得我們可以方便地跟蹤每一個狀態(tài)的變化,從而讓我們能夠?qū)崿F(xiàn)一些工具幫助我們更好地了解我們的應(yīng)用
Vuex的數(shù)據(jù)流是組件中觸發(fā)Action,Action提交Mutations,Mutations修改State。 組件根據(jù) States或Getters來渲染頁面。
參考官網(wǎng)的圖示Vuex官網(wǎng)。
二、Vuex基本使用
【前端面試刷題網(wǎng)站:靈題庫,收集大廠面試真題,相關(guān)知識點詳細(xì)解析。】
注意這里使用Vuex版本為4
基本使用
首先我們來使用Vuex中的createStore方法創(chuàng)建一個store,我們注意到創(chuàng)建store的對象中目前有state和mutations兩個屬性。state中保存著我們需要使用的數(shù)據(jù) count,mutations里面是改變state中數(shù)據(jù)的方法,方法接受一個state參數(shù),通過改變這個state參數(shù)的屬性(count)的值就可以修改狀態(tài)了。
執(zhí)行了store.commit('increase');之后,Vuex會執(zhí)行mutations中的increase方法,讓count加一。
import {createStore} from 'vuex';
const store = createStore({
state: {
count: 0
},
mutations: {
increase(state) {
state.count++;
}
}
});
store.commit('increase');
console.log(store.state.count); // 1
mutations還可以接收參數(shù)
import {createStore} from 'vuex';
const store = createStore({
state: {
count: 0
},
mutations: {
increase(state, {count}) {
state.count = count;
}
}
});
store.commit('increase', {count: 2});
console.log(store.state.count); // 2
getters
getters和state的關(guān)系類似于Vue組件data屬性和computed屬性的關(guān)系,getters根據(jù)state或者其他getters計算出另一個變量的值,當(dāng)其依賴的數(shù)據(jù)變化時候,它也會實時更新。
import {createStore} from 'vuex';
const store = createStore({
state: {
count: 0
},
getters: {
msg(state) {
return state.count % 2 === 0 ? '偶數(shù)': '奇數(shù)';
}
},
mutations: {
increase(state) {
state.count++;
}
}
});
store.commit('increase');
console.log(store.state.count, store.getters.msg); // 1 "奇數(shù)"
actions
既然已經(jīng)有了mutations可以改變state,為什么還需要actions呢?因為mutations不應(yīng)該用于異步修改狀態(tài)。實際上mutations是可以異步修改狀態(tài)的,比如:
mutations: {
increase(state) {
setTimeout(() => {
state.count++;
}, 1000);
}
}
但是這樣做的話,Vuex是無法知道修改state.count的時機的,因為它是在異步回調(diào)里面指定的,因此Vuex無法在調(diào)試工具中打印出我們實際改變state的操作。
因此Vuex中有actions API,在actions中可以進行異步操作,在actions中可以提交mutations,也可以觸發(fā)其他的action。
import {createStore} from 'vuex';
const store = createStore({
state: {
count: 0
},
mutations: {
increase(state) {
state.count++;
}
},
actions: {
increase(context) {
setTimeout(() => {
// contex.dispatch可以用于觸發(fā)其他action
context.commit('increase');
}, 1000);
}
}
});
store.dispatch('increase');
console.log(store.state.count); // 0
setTimeout(() => {
console.log(store.state.count); // 1
}, 1000);
三、Vuex modules
通常在一個web應(yīng)用中,會有很多數(shù)據(jù),都放在一個store里面會讓數(shù)據(jù)很混亂,因此我們應(yīng)該根據(jù)功能模塊將數(shù)據(jù)劃分成一個一個的模塊。
Vuex支持將store劃分成模塊,并且模塊還可以嵌套。
下面看官方文檔上的多個模塊的示例:
在調(diào)用createStore時候傳入的對象中,提供modules屬性,傳入兩個子模塊。
import {createStore} from 'vuex';
const moduleA = {
state: {
name: 'a'
}
};
const moduleB = {
state: {
name: 'b'
}
};
const store = createStore({
modules: {
a: moduleA,
b: moduleB
}
});
console.log(store.state.a.name); // a
console.log(store.state.b.name); // b
看下包含嵌套子模塊時候,訪問子模塊的getters和state的示例,
下面的示例中,有兩個子模塊a和b,其中a中還有嵌套的子模塊c,
(注意默認(rèn)情況下,所有子模塊的getters、mutations和actions都是注冊在全局的)
import {createStore} from 'vuex';
const store = createStore({
state: {
counter: 0
},
getters: {
counter10times(state) {
return state.counter * 10;
}
},
modules: {
a: {
state: {aName: 'A·a'},
aGetters: {
aFirstName(state) {
return state.aName.split('·')[0];
}
},
modules: {
c: {
state: {cName: 'C·c'},
getters: {
cFirstName(state) {
return state.cName.split('·')[0];
}
}
}
}
},
b: {
state: {bName: 'B·b'},
getters: {
bFirstName(state) {
return state.bName.split('·')[0];
},
bNewName(state, getters, rootState, rootGetters) {
// 訪問局部state
const {bName} = state;
// 訪問全局state
const {a: {c: {cName}}} = rootState;
// 訪問局部getters
const {bFirstName} = getters;
// 訪問全局getters
const {cFirstName} = rootGetters;
return `${bName} ${bFirstName} ${cName} ${cFirstName}`;
}
}
}
}
});
// 子模塊的state通過子模塊路徑訪問
console.log(store.state.a.c.cName);
// 子模塊的getters都注冊到了全局,在store.getters下面直接能訪問到
console.log(store.getters.bNewName);
下面是一個多模塊,commit mutation和dispatch action的示例,
模塊可以提交其他模塊的mutation,從而改變其他的模塊的狀態(tài);模塊也可以觸發(fā)其他模塊的action
import {createStore} from 'vuex';
const store = createStore({
state: {
counter: 0
},
mutations: {
increaseCounter(state) {
state.counter++;
}
},
modules: {
a: {
state: {aName: 'A·a'},
mutations: {
changeAName(state) {
state.aName = 'A-a';
}
},
actions: {
callChangeCNameAsync({dispatch}) {
// 觸發(fā)其他模塊的action
setTimeout(() => {
dispatch('changeCNameAsync');
}, 500);
}
},
modules: {
c: {
state: {cName: 'C·c'},
mutations: {
changeCName(state, payload) {
state.cName = `C-c-${payload.suffix}`;
}
},
actions: {
changeCNameAsync({commit, rootState}) {
setTimeout(() => {
// 提交其他模塊的mutation,mutation是全局的
commit('increaseCounter');
// 提交局部模塊的mutation
commit('changeCName', {
suffix: rootState.counter
});
}, 500);
}
}
},
}
},
b: {
state: {bName: 'B·b'},
mutations: {
changeBName(state) {
state.bName = 'B-b';
}
}
}
}
});
// 全局的commit
store.commit('increaseCounter');
console.log(store.state.counter); // 1
// 子模塊mutation注冊到全局了
store.commit('changeCName', {suffix: '123'});
console.log(store.state.a.c.cName); // C-c-123
// 子模塊commit其他模塊的mutation
store.dispatch('changeCNameAsync');
setTimeout(() => {
console.log(store.state.a.c.cName); // C-c-2
}, 1000);
// 子模塊dispatch其它模塊的action
store.dispatch('callChangeCNameAsync');
setTimeout(() => {
console.log(store.state.a.c.cName); // C-c-3
}, 1500);
之前提到默認(rèn)情況下,所有模塊的getters、mutations和actions都是注冊到全局的,這樣如果多個子模塊的getters、mutations和actions中有同名時候,會導(dǎo)致覆蓋,引起問題。因此通常我們需要給子模塊加命名空間。
給子模塊加命名空間的方式是給子模塊加namespaced屬性并賦值為true。
加了命名空間后,訪問state的方式不變(因為默認(rèn)state也不是注冊到全局的),訪問getters時候需要加命名空間前綴,如果訪問模塊自身子模塊的getters、提交mutations、觸發(fā)actions時候,只需要加相對路徑前綴,不需要加自身命名空間前綴,例如模塊a訪問其子模塊c時候,不需要加'a/c'前綴,只需要'c'就可以了。
看下多模塊(包含嵌套模塊情況)時候訪問state和getters的示例:
import {createStore} from 'vuex';
const store = createStore({
state: {
counter: 0
},
getters: {
counter10times(state) {
return state.counter * 10;
}
},
modules: {
a: {
namespaced: true,
state: {aName: 'A·a'},
getters: {
aFirstName(state) {
return state.aName.split('·')[0];
}
},
modules: {
c: {
namespaced: true,
state: {cName: 'C·c'},
getters: {
cFirstName(state) {
return state.cName.split('·')[0];
}
}
}
}
},
b: {
namespaced: true,
state: {bName: 'B·b'},
getters: {
bNewName(state, getters, rootState, rootGetters) {
// 局部state
const bName = state.bName.split('·')[0];
// 其他模塊的getter
const cFirstName = rootGetters['a/c/cFirstName'];
// 其他模塊的state
const aName = rootState.a.aName;
return `${bName} ${cFirstName} ${aName}`;
}
}
}
}
});
// getters命名空間
console.log(store.getters['b/bNewName']); // B C A·a
// 子節(jié)點state仍然是通過節(jié)點路徑訪問
console.log(store.state.a.c.cName); // C·c
看下在使用了命名空間的多模塊的提交mutations和觸發(fā)actions
import {createStore} from 'vuex';
const store = createStore({
state: {
counter: 0
},
mutations: {
increaseCounter(state) {
state.counter++;
}
},
modules: {
a: {
namespaced: true,
state: {aName: 'A·a'},
mutations: {
changeAName(state) {
state.aName = 'A-a';
}
},
actions: {
callChangeCNameAsync({dispatch}) {
// 觸發(fā)子模塊的action,是相對于自身的路徑,不需要加a前綴
setTimeout(() => {
dispatch('c/changeCNameAsync');
}, 500);
}
},
modules: {
c: {
namespaced: true,
state: {cName: 'C·c'},
mutations: {
changeCName(state, payload) {
state.cName = `C-c-${payload.suffix}`;
}
},
actions: {
changeCNameAsync({commit, rootState}) {
setTimeout(() => {
// 提交其他模塊的mutation,mutation是全局的
commit('increaseCounter', null, {root: true});
// 提交局部模塊的mutation,不需要加前綴
commit('changeCName', {
suffix: rootState.counter
});
}, 500);
}
}
},
}
},
b: {
namespaced: true,
state: {bName: 'B·b'},
mutations: {
changeBName(state) {
state.bName = 'B-b';
}
}
}
}
});
// 全局的commit
// 注意加了命名空間之后,提交根模塊的mutation和觸發(fā)根模塊的action時候,都需要加上{root: true}的選項
store.commit('increaseCounter', null, {root: true});
console.log(store.state.counter); // 1
// 子模塊mutation注冊到全局了
store.commit('a/c/changeCName', {suffix: '123'});
console.log(store.state.a.c.cName); // C-c-123
// 子模塊commit其他模塊的mutation
store.dispatch('a/c/changeCNameAsync');
setTimeout(() => {
console.log(store.state.a.c.cName); // C-c-2
}, 1000);
// 子模塊dispatch其它模塊的action
store.dispatch('a/callChangeCNameAsync');
setTimeout(() => {
console.log(store.state.a.c.cName); // C-c-3
}, 1500);
四、在Vue組件中使用Vuex
1. 注入store
使用Vue3、Vuex4版本,通過如下方式向注入store,
import { createApp } from 'vue';
import App from './App.vue';
import {createStore} from 'vuex';
const store = createStore({
state: {
counter: 0
},
getters: {
counter10times(state) {
return state.counter * 10;
}
},
mutations: {
increaseCounter(state) {
state.counter++;
}
},
modules: {
a: {
namespaced: true,
state: {aName: 'A·a'},
getters: {
aFirstName(state) {
return state.aName.split('·')[0];
}
},
mutations: {
changeAName(state) {
state.aName = 'A-a';
}
},
actions: {
callChangeCNameAsync({dispatch}) {
// 觸發(fā)子模塊的action,相對與自身的路徑
setTimeout(() => {
dispatch('c/changeCNameAsync');
}, 500);
}
},
modules: {
c: {
namespaced: true,
state: {cName: 'C·c'},
getters: {
cFirstName(state) {
return state.cName.split('·')[0];
}
},
mutations: {
changeCName(state, payload) {
state.cName = `C-c-${payload.suffix}`;
}
},
actions: {
changeCNameAsync({commit, rootState}) {
setTimeout(() => {
// 提交其他模塊的mutation,mutation是全局的
commit('increaseCounter', null, {root: true});
// 提交局部模塊的mutation,不需要加前綴
commit('changeCName', {
suffix: rootState.counter
});
}, 500);
}
}
}
}
},
b: {
namespaced: true,
state: {bName: 'B·b'},
getters: {
bNewName(state, getters, rootState, rootGetters) {
// 局部state
const bName = state.bName.split('·')[0];
// 其他模塊的getter
const cFirstName = rootGetters['a/c/cFirstName'];
// 其他模塊的state
const aName = rootState.a.aName;
return `${bName} ${cFirstName} ${aName}`;
}
},
mutations: {
changeBName(state) {
state.bName = 'B-b';
}
}
}
}
});
createApp(App).use(store).mount('#app');
將剛才的加了命名空間的store注入到Vue組件樹中。這樣在所有的Vue組件中,都能夠通過this.$store方式訪問store。
Vue組件通過computed屬性來監(jiān)聽store的數(shù)據(jù)變化。
看下面的示例,computed依賴了this.$store里面的一些模塊的state和getters,并將計算結(jié)果展示在界面上。
<template>
<div>
{{counter}}
{{bName}}
{{cFirstName}}
</div>
</template>
<script>
export default {
computed: {
counter() {
return this.$store.state.counter;
},
bName() {
return this.$store.state.b.bName;
},
cFirstName() {
return this.$store.getters['a/c/cFirstName'];
}
}
}
</script>
<style>
#app {
margin-top: 60px;
}
</style>
下面看下Vue組件中改變狀態(tài)的示例,
可以看到Vue組件中通過methods方法調(diào)用this.$store的commit和dispatch方法來提交修改和觸發(fā)action。
/**
* @file main.js
*/
import { createApp } from 'vue';
import App from './App.vue';
import {createStore} from 'vuex';
const store = createStore({
state: {
counter: 0
},
getters: {
counter10times(state) {
return state.counter * 10;
}
},
mutations: {
increaseCounter(state) {
state.counter++;
}
},
modules: {
a: {
namespaced: true,
state: {aName: 'A·a'},
getters: {
aFirstName(state) {
return state.aName.split('·')[0];
}
},
mutations: {
changeAName(state) {
state.aName = 'A-a';
}
},
actions: {
callChangeCNameAsync({dispatch}) {
// 觸發(fā)子模塊的action,相對于自身的路徑
setTimeout(() => {
dispatch('c/changeCNameAsync');
}, 500);
}
},
modules: {
c: {
namespaced: true,
state: {cName: 'C·c'},
getters: {
cFirstName(state) {
return state.cName.split('·')[0];
}
},
mutations: {
changeCName(state, payload) {
state.cName = `C-c-${payload.suffix}`;
}
},
actions: {
changeCNameAsync({commit, rootState}) {
setTimeout(() => {
// 提交其他模塊的mutation,mutation是全局的
commit('increaseCounter', null, {root: true});
// 提交局部模塊的mutation,不需要加前綴
commit('changeCName', {
suffix: rootState.counter
});
}, 500);
}
}
}
}
},
b: {
namespaced: true,
state: {bName: 'B·b'},
getters: {
bNewName(state, getters, rootState, rootGetters) {
// 局部state
const bName = state.bName.split('·')[0];
// 其他模塊的getter
const cFirstName = rootGetters['a/c/cFirstName'];
// 其他模塊的state
const aName = rootState.a.aName;
return `${bName} ${cFirstName} ${aName}`;
}
},
mutations: {
changeBName(state) {
state.bName = 'B-b';
}
}
}
}
});
createApp(App).use(store).mount('#app');
/**
* @file App.vue
*/
<template>
<div>
{{counter}}
{{bName}}
{{cFirstName}}
<button @click="modifyBName">修改b的name</button>
<button @click="modifyCName">修改c的name</button>
</div>
</template>
<script>
export default {
computed: {
counter() {
return this.$store.state.counter;
},
bName() {
return this.$store.state.b.bName;
},
cFirstName() {
return this.$store.getters['a/c/cFirstName'];
}
},
methods: {
modifyBName() {
this.$store.commit('b/changeBName');
},
modifyCName() {
this.$store.dispatch('a/callChangeCNameAsync');
}
}
}
</script>
<style>
#app {
margin-top: 60px;
}
</style>
2. 輔助函數(shù)
我們知道我們使用Vuex時候,通過computed綁定store的state和getters的數(shù)據(jù),通過methods中調(diào)用this.$store的commit和dispatch方法來改變狀態(tài)。
但是每次都要寫this.$store,當(dāng)需要綁定的數(shù)據(jù)多的時候會比較繁雜,因此Vuex提供了輔助函數(shù)來簡化代碼。輔助函數(shù)包括
- mapState
- mapGetters
- mapMutations
- mapActions
其中mapState和mapGetters將映射到computed屬性中,mapMutations和mapActions映射到methods屬性中。
用法見下面示例
<template>
<div>
{{counter}}
{{bName}}
{{counter10times}}
{{cFirstName}}
<button @click="modifyBName">修改b的name</button>
<button @click="modifyCName">修改c的name</button>
</div>
</template>
<script>
import {mapState, mapGetters, mapMutations, mapActions} from 'vuex'
export default {
computed: {
...mapState({
// 將this.$store.state.counter映射為counter
counter: state => state.counter,
// 也可以這樣實現(xiàn)
// counter: 'counter',
bName: state => state.b.bName
}),
// 全局的getters
...mapGetters(['counter10times']),
// 也可以這樣實現(xiàn),指定組件中的數(shù)據(jù)名稱
// ...mapGetters({
// counter10times: 'counter10times'
// }),
// 子模塊的getters
...mapGetters({
cFirstName: 'a/c/cFirstName'
}),
// 帶有命名空間的子模塊也可以這樣實現(xiàn)映射,在方法多的時候可以簡化代碼
// ...mapGetters('a/c', [
// 'cFirstName'
// ])
},
methods: {
// 映射mutations到方法
...mapMutations({
modifyBName: 'b/changeBName'
}),
// 也可以這樣實現(xiàn)
// ...mapMutations('b', {
// modifyBName: 'changeBName'
// }),
// 帶有命名空間的子模塊映射到組件的方法
...mapActions('a', {
modifyCName: 'callChangeCNameAsync'
}),
}
}
</script>
<style>
#app {
margin-top: 60px;
}
</style>
3. 組件之間共享數(shù)據(jù)
上面說明了Vuex的使用方法,下面看下Vuex在組件共享數(shù)據(jù)場景的一個簡單示例。
有兩個子組件bChild和cChild,它們直接從store中獲取數(shù)據(jù)并渲染。在根組件App.vue中修改store中的數(shù)據(jù),可以看到子組件會相應(yīng)數(shù)據(jù)更新,展示最新的數(shù)據(jù)。
<template>
<div>
{{counter}}
{{counter10times}}
<b-child></b-child>
<c-child></c-child>
<button @click="modifyBName">修改b的name</button>
<button @click="modifyCName">修改c的name</button>
</div>
</template>
<script>
import {mapState, mapGetters, mapMutations, mapActions} from 'vuex'
import bChild from './components/bChild';
import cChild from './components/cChild';
export default {
computed: {
...mapState({
counter: state => state.counter,
// 也可以這樣實現(xiàn)
// counter: 'counter',
}),
// 全局的getters
...mapGetters(['counter10times']),
// 也可以這樣實現(xiàn),指定組件中的數(shù)據(jù)名稱
// ...mapGetters({
// counter10times: 'counter10times'
// }),
},
components: {
'b-child': bChild,
'c-child': cChild
},
methods: {
// 映射mutations到方法
...mapMutations({
modifyBName: 'b/changeBName'
}),
// 也可以這樣實現(xiàn)
// ...mapMutations('b', {
// modifyBName: 'changeBName'
// }),
// 帶有命名空間的子模塊映射到組件的方法
...mapActions('a', {
modifyCName: 'callChangeCNameAsync'
}),
}
}
</script>
<style>
#app {
margin-top: 60px;
}
</style>
五、Vuex原理
1. 說明
Vuex通過createStore創(chuàng)建了一個數(shù)據(jù)中心,然后通過發(fā)布-訂閱模式來讓訂閱者監(jiān)聽到數(shù)據(jù)改變。
那么Vuex是怎么應(yīng)用到Vue中的呢?
先來看一個在Vue中使用Vuex的簡單例子:
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import {createStore} from 'vuex';
const store = createStore({
state: {
message: 'hello'
},
mutations: {
change(state) {
state.message = 'world';
}
}
});
createApp(App).use(store).mount('#app');
export default {
name: 'App',
computed: {
info() {
return this.$store.state.message;
}
},
mounted() {
this.$store.commit('change');
}
}
可以看到,在Vue中使用Vuex,主要有3個關(guān)鍵步驟:
- 使用Vuex創(chuàng)建store,再將store注入Vue中。Vue組件中就可以通過this.$store來訪問到store。
- Vue使用computed獲取$store中的狀態(tài)。
- Vue通過
store.action來修改狀態(tài)。
那么我們需要問兩個問題:
- 注入的原理是什么?為什么調(diào)用use()方法之后,就可以在組件通過$store來訪問store了?
- 響應(yīng)式原理是什么?為什么使用computed可以監(jiān)聽到store中的狀態(tài)改變?
這兩個是Vuex比較核心的兩個原理。
2. 注入原理
store注入 vue的實例組件的方式,是通過vue的 mixin機制,借助vue組件的生命周期鉤子beforeCreate 完成的。
Vue.mixin({
beforeCreate() {
if (this.$options && this.$options.store) {
// 找到根組件 main 上面掛一個$store
this.$store = this.$options.store;
}
else {
// 非根組件指向其父組件的$store
this.$store = this.$parent && this.$parent.$store;
}
}
});
3. Vuex響應(yīng)式原理
Vuex使用vue中的reactive
方法將state設(shè)置為響應(yīng)式,原理和Vue組件的data設(shè)置為響應(yīng)式是一樣的。
// vuex/src/store-util.js
import {reactive} from 'vue';
store._state = reactive({
data: state
});
4. 總結(jié)
Vuex是個狀態(tài)管理器。
它Vuex通過createStore創(chuàng)建了一個數(shù)據(jù)中心,然后通過發(fā)布-訂閱模式來讓訂閱者監(jiān)聽到數(shù)據(jù)改變。
Vuex的store注入 vue的實例組件的方式,是通過vue的 mixin機制,借助vue組件的生命周期鉤子beforeCreate 完成的。這樣Vue組件就能通過this.$store獲取到store了。
Vuex使用vue中的reactive
方法將state設(shè)置為響應(yīng)式,這樣組件就可以通過computed來監(jiān)聽狀態(tài)的改變了。