天天看点

前端面试必备: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实现数据的实时监听。