Vuex 面試題
1. 什么是Vuex?
Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態, 並以相應的規則保證狀態以一種可預測的方式發生改變 簡單來說,就是用來集中管理數據;
2. Vuex解決了什么問題?
解決兩個問題:
- 多個組件依賴於同一狀態時,對於多層嵌套的組件的傳參將會非常繁瑣,並且對於兄弟組件間的狀
態傳遞無能為力。 - 來自不同組件的行為需要變更同一狀態。以往采用父子組件直接引用或者通過事件來變更和同步狀
態的多份拷貝。以上的這些模式非常脆弱,通常會導致無法維護的代碼。
3. 使用vuex的核心概念
1)store
vuex 中最關鍵的是 store 對象,這是 vuex 的核心。可以說,vuex 這個插件其實就是一個 store 對象,每
個 vue 應用僅且僅有一個 store 對象。
(1)創建store
const store = new Vuex.Store({...});
可見,store是Vuex.Store這個構造函數new出來的實例。在構造函數中可以傳一個對象參數。這個參數中可以包含5個對象:
- state – 存放狀態
- getters – state的計算屬性
- mutations – 更改狀態的邏輯,同步操作
- actions – 提交mutation,異步操作
- mudules – 將store模塊化
關於store,需要先記住兩點:
- store 中存儲的狀態是響應式的,當組件從store中讀取狀態時,如果store中的狀態發生了改變,
那么相應的組件也會得到更新; - 不能直接改變store中的狀態。改變store中的狀態的唯一途徑是提交(commit)mutations。這樣使
得我們可以方便地跟蹤每一個狀態的變化。
(2)完整的store的結構
const store = new Vuex.Store({
state: {
// 存放狀態
},
getters: {
// state的計算屬性
},
mutations: {
// 更改state中狀態的邏輯,同步操作
},
actions: {
// 提交mutation,異步操作
},
// 如果將store分成一個個的模塊的話,則需要用到modules。
//然后在每一個module中寫state, getters, mutations, actions等。
modules: {
a: moduleA,
b: moduleB,
// ...
}
2) state
state上存放的,說的簡單一些就是變量,也就是所謂的狀態。沒有使用 state 的時候,我們都是直接在 data 中進行初始化的,但是有了 state 之后,我們就把 data 上的數據轉移到 state 上去了。另外有些狀態是組件私有的狀態,稱為組件的局部狀態,我們不需要把這部分狀態放在 store 中去。
(1)如何在組件中獲取 vuex 狀態
由於 vuex 的狀態是響應式的,所以從 store 中讀取狀態的的方法是在組件的計算屬性中返回某個狀態。
import store from 'store';
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
// 獲取store中的狀態
return store.state.count;
}
}
}
這樣,組件中的狀態就與 store 中的狀態關聯起來了。每當 store.state.count 發生變化時,都會重新求取計算屬性,從而更新 DOM。
然而,每個組件中都需要反復倒入 store。可以將 store 注入到 vue 實例對象中去,這樣每一個子組件中都可以直接獲取 store 中的狀態,而不需要反復的倒入 store 了。
const app = new Vue({
el: '#app',
// 把 store 對象注入到了
store,
components: { Counter },
template: `
<div>
<counter></counter>
</div>
`
});
這樣可以在子組件中使用 this.$store.state.count 訪問到 state 里面的 count 這個狀態
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
// 獲取store中的狀態
return this.$store.state.count;
}
}
}
(2) mapState
當一個組件獲取多種狀態的時候,則在計算屬性中要寫多個函數。為了方便,可以使用 mapState 輔助函數來幫我們生成計算屬性。
import { mapState } from 'vuex';
export default {
// ...
data (){
localState: 1
}
computed: mapState({
// 此處的state即為store里面的state
count: state => state.count,
// 當計算屬性的名稱與state的狀態名稱一樣時,可以省寫
// 映射 this.count1 為 store.state.count1
count1,
//'count'等同於 ‘state => state.count’
countAlias: 'count',
countPlus (state){
// 使用普通函數是為了保證this指向組件對象
return state.count + this.localState;
}
})
}
//上面是通過mapState的對象來賦值的,還可以通過mapState的數組來賦值
computed: mapState(['count']);
//這種方式很簡潔,但是組件中的state的名稱就跟store中映射過來的同名
對象擴展運算符
mapState 函數返回的是一個對象,為了將它里面的計算屬性與組件本身的局部計算屬性組合起來,需要用到對象擴展運算符。
computed: {
localState () {
...mapState ({
})
}
}
這樣,mapState 中的計算屬性就與 localState 計算屬性混合一起了。
3)getters
有時候我們需要從 store 中的 state 中派生出一些狀態,例如對列表進行過濾並計數。此時可以用到 getters,getters 可以看作是 store 的計算屬性,其參數為 state。
const store = new Vuex.Store({
state: {
todos: [
{id: 1, text: 'reading', done: true},
{id: 2, text: 'playBastketball', done: false}
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done);
}
}
});
(1)獲取getters里面的狀態,方法一
store.getters.doneTodos // [{ id: 1, text: 'reading', done: true }]
//在組件中,則要寫在計算屬性中,
computed: {
doneTodos () {
return this.$store.getters.doneTodos;
}
}
(2) 使用mapGetters獲取getters里面的狀態:方法二
import {mapState, mapGetters} from 'vuex';
computed: {
...mapState(['increment']),
...mapGetters(['doneTodos'])
}
4)mutations
mutations 里面是如何更改 state 中狀態的邏輯。更改 Vuex 中的 state 的唯一方法是,提交 mutation,即 store.commit(‘increment’) 。
(1) 提交載荷 (payload)
可以向 commit 傳入額外的參數,即 mutation 的載荷。
mutations: {
increment(state, n){
state.count += n;
}
}
store.commit('increment', 10);
payload 還可以是一個對象。
mutations: {
increment(state, payload)
state.count += payload.amount;
}
store.commit('increment', {amount: 10});
還可以使用 type 屬性來提交 mutation。
store.commit({
type: 'increment',
amount: 10
});
// mutations保持不變
mutations: {
increment(state, payload){
state.count += payload.amount;
}
}
注意:mutation 必須是同步函數,不能是異步的,這是為了調試的方便。
(2)在組件中提交 mutations
那么 mutation 應該在哪里提交呢? 因為js是基於事件驅動的,所以改變狀態的邏輯肯定是由事件來驅動的,所以 store.commit(‘increment’) 是在組件的 methods 中來執行的。
方法1: 在組件的 methods 中提交
methods: {
increment(){
this.$store.commit('increment');
}
}
方法2: 使用 mapMutaions
用 mapMutations 輔助函數將組件中的 methods 映射為 store.commit 調用。
import { mapMutaions } from 'vuex';
export default {
// ...
methods: {
...mapMutaions([
'increment' // 映射 this.increment() 為 this.$store.commit('increment')
]),
...mapMutaions([
add: 'increment' // 映射 this.add() 為 this.$store.commit('increment')
])
}
}
// 因為mutation相當於一個method,所以在組件中,可以這樣來使用
<button @click="increment">+</button>
5)actions
因為 mutations 中只能是同步操作,但是在實際的項目中,會有異步操作,那么 actions 就是為了異步操作而設置的。這樣,就變成了在 action 中去提交 mutation,然后在組件的 methods 中去提交 action。只是提交 actions的時候使用的是 dispatch 函數,而 mutations 則是用 commit 函數。
(1)一個簡單的 action
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state){
state.count++;
}
},
actions: {
increment(context){
context.commit('increment');
}
/* 可以用參數結構的方法來寫action
increment({commit}){
commit('increment');
}
*/
}
});
// action函數接受一個context參數,這個context具有與store實例相同的方法和屬性。
// 分發action
store.dispatch('increment');
action 同樣支持 payload 和對象方式來分發,格式跟 commit 是一樣的,不再贅述。
(2)在組件中分發 action
方法1: 在組件的 methods 中,使用 this.$store.dispatch(‘increment’)
。
方法2: 使用 mapActions,跟 mapMutations 是類似的。
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment' // 映射 this.increment() 為 this.$store.dispatch('increment')]),
...mapActions({
add: 'increment' // 映射 this.add() 為 this.$store.dispatch('increment')
})
}
}
// 同樣在組件中,可以這樣來使用
<button @click="increment">+</button>
(3)組合 actions
因為 action 是異步的,那么我們需要知道這個異步函數什么時候結束,以及等到其執行后,會利用某個 action 的結果。這個可以使用 promise 來實現。在一個 action 中返回一個 promise,然后使用 then() 回調函數來處理這個 action 返回的結果。
actions:{
actionA({commit}){
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation');
resolve();
},1000);
})
}
}
// 這樣就可以操作actionA返回的結果了
store.dispatch('actionA').then(() => {
// dosomething ...
});
// 也可以在另一個action中使用actionA的結果
actions: {
// ...
actionB({ dispatch, commit }){
return dispatch('actionA').then(() => {
commit('someOtherMutation');
})
}
}
更多面試題:119頁Vue面試題總結可【點擊此處免費領取!】