天天看點

前端面試必備:Vuex的簡單使用及原理分析

在開發中,我們會用應用程式處理很多的資料,這些資料需要儲存在我們應用程式的某一個位置,對于這些資料的管理我們就稱之為狀态管理。

Vuex是一個專門為Vue.js應用程式開發的狀态管理模式。它采用集中式存儲管理應用的所有元件的狀态,解決多元件資料通信問題。

一、Vuex核心

Vuex 可以幫助我們管理共享狀态,并附帶了更多的概念和架構。這需要對短期和長期效益進行權衡。

如果你不打算開發大型單頁應用,使用 Vuex 可能是繁瑣備援的。确實是如此——如果你的應用夠簡單,您最好不要使用 Vuex。一個簡單的 store 模式 (opens new window)就足夠你所需了。但是,如果你需要建構一個中大型單頁應用,你很可能會考慮如何更好地在元件外部管理狀态,Vuex 将會成為自然而然的選擇。

VueX是采用集中式管理元件依賴的共享資料的一個工具,可以解決不同元件資料共享問題。

前端面試必備:Vuex的簡單使用及原理分析

1、State:

包含了store中存儲的各個資料。

用法:

const store = new Vuex.Store({

state:{

count:0

}

})

元件通路State中資料的三種方式:

  • this.$store.state.count

<template>

<div> state的資料:{{ $store.state.count }}</div>

<template>

<script>

// 安裝完 vuex 會在元件中注入一個 $store 的變量

created() {

console.log(this.$store.state.count);

}

</script>

  • 使用計算屬性computed

<template>

<div> state的資料:{{ count }}</div>

<template>

<script>

// 把state中資料,定義在元件内的計算屬性中

computed: {

count () {

return this.$store.state.count

}

}

</script>

每當 $store.state.count 變化的時候, 都會重新求取計算屬性,并且觸發更新相關聯的 DOM。

然而,這種模式導緻元件依賴全局狀态單例。在子產品化的建構系統中,在每個需要使用 state 的元件中需要頻繁地導入,并且在測試元件時需要模拟狀态。

  • 借助mapState輔助函數映射

<template>

<div> state的資料:{{ count }}</div>

<template>

import {mapState} from 'vuex'

export default{

computed:{

...mapState(['count'])

}

}

2、Getter

除了state之外,有時我們還需要從state中派生出一些狀态,這些狀态是依賴state的,此時會用到getters。類似于Vue中的計算屬性,根據getter或者state計算傳回值。

例如:對清單進行過濾并計數

const store = new Vuex.Store({

state: {

count: [1,2,3,4,5,6,7,8,9,10]

},

getters: {

// getters函數的第一個參數是 state

// 必須要有傳回值

getCount: state => state.list.filter(item => item > 5)

}

})

元件通路getters中資料的兩種方式:

  • this.$store.getters.getCount

<template>

<div>

<div> state的資料:{{ getCount }}</div>

</div>

<template>

<script>

// 把state中資料,定義在元件内的計算屬性中

computed: {

getCount () {

return this.$store.getters.getCount

}

}

</script>

  • 借助mapGetters輔助函數映射

import {mapGetters} from 'vuex'

export default{

computed:{

...mapGetters(['getCount'])

}

}

3、Mutation

Vuex裡store狀态更新的唯一方法,且必須是同步函數。

用法:

const store = new Vuex.Store({

state:{

count:0

},

mutations:{

// 接受 state作為第一個參數

mutationCount(state){

state.count ++ ;

}

// 向 store.commit 傳入額外的參數,即 mutation 的 載荷(payload)

addCount (state, payload) {

state.count += payload.data

}

}

})

元件通路mutations中資料的兩種方式:

  • 使用store.commit調用mutation。

methods:{

changeCount(){

this.$store.commit('mutationCount')

}

}

  • 借助mapMutations輔助函數映射

import {mapMutations} from 'vuex'

export default{

methods:{

...mapMutations(['mutationCount'])

}

}

在 mutation 中混合異步調用會導緻你的程式很難調試。

mutations: {

someMutation (state) {

setTimeout(() => {

state.count++

},1000)

}

}

現在想象,我們正在debug一個app并且觀察 devtool中的mutation日志。每一條mutation被記錄,devtools都需要捕捉到前一狀态和後一狀态的快照。然而,在上面的例子中 mutation 中的異步函數中的回調讓這不可能完成:因為當 mutation 觸發的時候,回調函數還沒有被調用,devtools 不知道什麼時候回調函數實際上被調用——實質上任何在回調函數中進行的狀态的改變都是不可追蹤的。

是以,在 Vuex 中mutation 都是同步函數。

4、Action

用于送出mutation,而不是直接變更狀态。可異步。

Action類似于Mutation,不同的在于:

(1)Action送出的是Mutation,而不是直接變更狀态

(2)Action可以包含任何異步操作

用法:

const store = new Vuex.Store({

state:{

count:0

},

mutations:{

mutationCount(state,payload){

state.count = payload;

}

},

actions:{

actionCount(context,payload){

setTimeout(()=>{

context.commit('mutationCount',payload.count)

},1000)

}

}

})

元件通路actions中資料的兩種方式:

  • 使用store.dispatch來分發action。

methods:{

changeCountAsync(){

this.$store.dispatch('actionCount',res)

}

}

  • 借助mapActions輔助函數映射

import {mapActions} from 'vuex'

export default{

methods:{

...mapActions(['actionCount'])

}

}

最後,如果我們利用 async / await ,我們可以組合 action:

// 假設 getData() 和 getOtherData() 傳回的是 Promise

actions: {

async actionA ({ commit }) {

commit('gotData', await getData())

},

async actionB ({ dispatch, commit }) {

await dispatch('actionA') // 等待 actionA 完成

commit('gotOtherData', await getOtherData())

}

}

一個 store.dispatch 在不同子產品中可以觸發多個 action 函數。在這種情況下,隻有當所有觸發函數完成後,傳回的 Promise 才會執行。

5、 Module

允許将單一的Store拆分為多個store。

前端面試必備:Vuex的簡單使用及原理分析

由于使用單一狀态樹,應用的所有狀态會集中到一個比較大的對象。當應用變得非常複雜時,store 對象就有可能變得相當臃腫。

為了解決以上問題,Vuex 允許我們将 store 分割成子產品(module)。每個子產品擁有自己的 state、mutation、action、getter、甚至是嵌套子子產品——從上至下進行同樣方式的分割:

每個子產品都擁有獨立的state、getters、mutations、actions

const moduleA = {

state: () => ({ ... }),

mutations: { ... },

actions: { ... },

getters: { ... }

}

const moduleB = {

state: () => ({ ... }),

mutations: { ... },

actions: { ... }

}

const store = new Vuex.Store({

modules: {

a: moduleA,

b: moduleB

}

})

store.state.a // -> moduleA 的狀态

store.state.b // -> moduleB 的狀态

子產品的局部狀态

對于子產品内部的 mutation 和 getter,接收的第一個參數是子產品的局部狀态對象。

const moduleA = {

state: () => ({

count: 0

}),

mutations: {

increment (state) {

// 這裡的 `state` 對象是子產品的局部狀态

state.count++

}

},

getters: {

doubleCount (state) {

return state.count * 2

}

}

}

對于子產品内部的 getter,根節點狀态會作為第三個參數暴露出來:

const moduleA = {

// ...

getters: {

sumWithRootCount (state, getters, rootState) {

return state.count + rootState.count

}

}

}

命名空間

預設情況下,子產品内部的 action、mutation 和 getter 是注冊在全局命名空間的——這樣使得多個子產品能夠對同一 mutation 或 action 作出響應。

如果希望你的子產品具有更高的封裝度和複用性,你可以通過添加 namespaced: true 的方式使其成為帶命名空間的子產品。當子產品被注冊後,它的所有 getter、action 及 mutation 都會自動根據子產品注冊的路徑調整命名。例如:

const store = new Vuex.Store({

modules: {

account: {

namespaced: true,

// 子產品内容(module assets)

state: () => ({ ... }), // 子產品内的狀态已經是嵌套的了,使用 `namespaced` 屬性不會對其産生影響

getters: {

isAdmin () { ... } // -> getters['account/isAdmin']

},

actions: {

login () { ... } // -> dispatch('account/login')

},

mutations: {

login () { ... } // -> commit('account/login')

},

// 嵌套子產品

modules: {

// 繼承父子產品的命名空間

myPage: {

state: () => ({ ... }),

getters: {

profile () { ... } // -> getters['account/profile']

}

},

// 進一步嵌套命名空間

posts: {

namespaced: true,

state: () => ({ ... }),

getters: {

popular () { ... } // -> getters['account/posts/popular']

}

}

}

}

}

})

啟用了命名空間的 getter 和 action 會收到局部化的 getter,dispatch 和 commit。換言之,你在使用子產品内容(module assets)時不需要在同一子產品内額外添加空間名字首。更改 namespaced 屬性後不需要修改子產品内的代碼。

二、Vue3中使用Vuex

1. 安裝Vuex。

npm install vuex --save

2. 配置Vuex。

在src/store/index.js中配置

import {createStore} from 'vuex';

export default createStore({

})

3. 在main.js中引入Vuex。

import {createApp} from 'vue';

import App from './App.vue';

import {store} from './store';

createApp(App).use(store).mount('#app);

4. 在元件中讀取資料。

三、Vuex資料響應式原理

Vuex完美的結合了Vue的響應式資料。那麼它的原理是什麼呢?

每一個Vue的插件,都需要一個公開的install方法,Vuex也不例外。

// src/store.js

export function install (_Vue) {

if (Vue && _Vue === Vue) {

return

}

Vue = _Vue

applyMixin(Vue)//在所有元件的beforeCreate注入了設定this.$store這樣的一個對象,

//在任意元件中執行this.$store都能找到store對象

}

Vuex.Store的構造函數中,有一堆初始化以及resetStoreVM(this,state)整個Vuex的關鍵。

// src/store.js

function resetStoreVM (store, state, hot) {

// 省略無關代碼

Vue.config.silent = true

//本質:将我們傳入的state作為一個隐藏的vue元件的data

//也就是說,我們的commit操作,本質上其實是修改這個元件的data

store._vm = new Vue({

data: {

$state: state

},

computed

})

}

//this.$store._vm.$data.$state===this.$store.state

Vuex的state狀态是響應式,是借助Vue的data是響應式,将state存入Vue執行個體元件的data中;Vuex的getters則是借助Vue的計算屬性computed實作資料實時監聽。

computed計算屬性監聽data資料變更主要經曆以下幾個過程:

前端面試必備:Vuex的簡單使用及原理分析

四、Vuex适用場景

1. Vuex一般用于中大型web單頁應用中對應用的狀态進行管理。

2. Vuex解決非父子元件之間的通信問題。

3. Vuex做為資料存儲中心。如購物車、登陸狀态。

五、總結

Vuex是針對Vuejs的狀态管理工具。Vuex完美的結合了Vue的響應式資料。

Vue可以直接觸發methods中的方法,Vuex不行。為了處理異步,當你觸發一個點選事件時,會通過dispatch來通路actions中的方法,actions中的commit會觸發mutations中的方法進而修改state的值,通過getters來把資料反映到視圖。

Vuex通過Vue.use(Vuex),進而調用install方法,通過applyMixin(Vue)在任意元件執行this.$store都能通路到store對象,實作将store挂載注入到元件中。

Vuex的state狀态是響應式的,是借助Vue的data是響應式,将state存入Vue執行個體元件的data中。

Vuex的getters則是借助于Vue的計算屬性computed實作資料的實時監聽。