前言
写项目很久了,偶尔用到Vuex也是用一些很浅显的功能,就是简单的存储一下用户信息,用的时候取一下,很少深入的使用,现在静下心来想给自己写个项目,在写的过程中,顺便把以往忽略的基础知识学习巩固一下,这篇文章就是记录一下学习Vuex的知识,既然是巩固知识,那那些基础的就直接一笔带过了。
本文面向对象也主要是初学者,所以用词更偏向于易懂,所以建议大家理解之后还是去看一下官方文档,熟悉一下专业术语,并且本文主要挑重点的讲,大家也可以去看一下官方文档查漏补缺。
介绍
什么是Vuex?
在日常的项目开发中,我们经常会遇到一些需要全局存储的变量,需要多个地方使用,比如用户信息,购物车等,在之前,我们采取的方案可能就是设置公共组件或者利用 cookie
或 localstorage
等本地存储方式进行存储,但这样的方法无疑会带来很多弊端,例如:
- 需要在多个模块频繁引用
- 存储格式限制,取值时候需要格式转换
- 存储结构不够清晰
- 不是响应式的
- 无法形成统一规范,接受别人代码需要一定时间理解
- 无法追踪值的修改记录
- ……
以上这些问题Vuex都可以统统为我们解决掉,下面我们就来看看Vuex官方是如何解释vuex的
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
以上是vuex官方文档对于vuex的解释,但对于初级vue开发者来说,这样的解释无疑太过官方,过于拗口也难以理解,其实用白话文解释一下,大致就是Vuex是一个仅能在Vue.js环境下使用的一个公共状态存储仓库,这个仓库里可以存储你所经常使用的全局变量,并且为你指定了规定的方式去修改仓库里的变量,并且如果你用这种方式修改仓库里的变量,那么所有的修改记录都可以被保存。
安装及使用
像Vue一样,我们也可以通过script标签引入的方式和模块安装的方式使用Vuex,通常我们会通过Vue-cli新建Vue项目,如果你选择了的话,Vue-cli会自动为我们的新项目安装并引入Vuex,此处不在赘述,后续的内容讲解以在此情况下展开。
Vuex核心概念
简述
Vuex的核心概念如下所示,包括state,Getters,mutations和Actions,下面我们就来分别深入了解他们的作用。
代码语言:javascript复制import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
getters
})
state
state 就是我们在 Vuex 中存储数据的地方,state 中的数据和 Vue 实例中的 data
一样,也必须以键值对的形式存在。
我们可以事先在state中定义好一个数据
代码语言:javascript复制export default new Vuex.Store({
state: {
count: 0
}
})
由于我们之前已经在Vue实例中通过 store
选项从根组件“注入”到每了一个子组件中,所以我们可以在所有的子组件中通过this.$store
访问store实例中的的内容
Vuex有一种官方推荐的使用方法,因为 Vuex 的状态存储是响应式的,所以Vuex鼓励我们使用Vue的计算属性去从store实例中读取state
代码语言:javascript复制<template>
<div id="app">
<div class="message">{{ count }}</div>
</div>
</template>
<script>
export default {
name: 'App',
computed: {
count () {
return this.$store.state.count
}
}
}
</script>
还有的同学会选择在 data 中去读取state中的值
代码语言:javascript复制<template>
<div id="app">
<div class="message">{{ count }}</div>
</div>
</template>
<script>
export default {
name: 'App',
data () {
return {
count: this.$store.state.count
}
}
}
</script>
这样子看上去显示正常,但会有一个隐患,那就是在 data
中获取store实例中的值,不是响应式的,data
中的值存储的只是 created
时候store实例中对应的值的字面量,后续该值发生任何变化,data
中的值都不会发生变化。下面我们可以来看一个例子。
<template>
<div id="app">
<div class="message">{{ count }}</div>
</div>
</template>
<script>
export default {
name: 'App',
data () {
return {
count: this.$store.state.count
}
},
mounted () {
setTimeout(() => {
this.$store.commit('increment', 10)
}, 2000)
}
}
</script>
可以看到,虽然我们在进入页面两秒后对store实例中的值进行了修改,我们的页面上却并没有跟着变化。
当然官方也没有推荐直接在模板中使用,虽然那样做一样可以达到效果。我也不知道为啥,有知道有什么区别的同学可以在评论区留言。
代码语言:javascript复制<template>
<div id="app">
<div class="message">{{ this.$store.state.count }}</div>
</div>
</template>
getters
getters官方讲的比较简单,这里就直接搬过来展示了。
有时候我们需要对取到的值进行计算之后进行展示,例如对列表进行过滤并计数
代码语言:javascript复制computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
Getter 接受 state 作为其第一个参数:
代码语言:javascript复制const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
Getter 也可以接受其他 getter 作为第二个参数:
代码语言:javascript复制getters: {
// ...
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
然后我们就可以在组件中直接使用了
代码语言:javascript复制computed: {
doneTodos () {
return this.$store.getters.doneTodos
},
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}
有时候我们会想给getter 传参,以便让它按照我们不同的需求返回不同的数据。
代码语言:javascript复制getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
代码语言:javascript复制store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
Mutations
Mutation的基本使用
注意:为了防止误解,特注明,Mutatios里的每一个函数都称为一个Mutation,所以当我们说一个Mutation的时候指的是Mutations中的一个函数
前面说了,Vuex给为我们指定了规定的方式去修改仓库里的变量,也就是state里的值,那就是去提交 mutation。因为Vuex 也集成到 Vue 的官方调试工具 devtools,所以我们可以很方便的通过devtools去查看Vuex里的值和相应的变化。
当你安装了vue-devtools之后,可以打开控制台,找到vue标签,点击第二个图标,即可开始Vuex的调试。
那我们如何去通过Mutation去修改state里的值呢?Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 **回调函数 (handler)**。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数,还可以接收第二个可选参数:
代码语言:javascript复制const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
testMutation (state, obj) {
state.count = obj.num
}
}
})
当我们在组件内调用时,需要通过commit方法去触发mutation,commit方法接受两个参数,第一个为要触发的mutation的名字,第二个参数为我们要传递过去的参数(可选),如果我们要传递多个参数,则可以将多个参数放入一个对象传递过去
代码语言:javascript复制this.$store.commit('increment', {num: 10,remarks: '测试' })
当然,我们还有另一种写法,这两种写法除了风格不同,作用完全相同
代码语言:javascript复制this.$store.commit({
type: 'increment',
num: 10,
remarks: '测试'
})
Mutation 需遵守 Vue 的响应规则
既然 Vuex 的 store 中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue 组件也会自动更新。这也意味着 Vuex 中的 mutation 也需要与使用 Vue 一样遵守一些注意事项:
- 最好提前在你的 store 中初始化好所有所需属性。
- 当需要在对象上添加新属性时,你应该
- 使用 Vue.set(obj, 'newProp', 123), 或者
- 以新对象替换老对象。例如,利用对象展开运算符我们可以这样写:
state.obj = { ...state.obj, newProp: 123 }
Mutation与devtools
在上面的示例中,我们已经知道如何通过devtools查看store实例里的值,而且我们也说了,通过官方指定的操作方式去修改state的值,会留下操作记录,那操作记录是什么样的呢,下面我们就直接来操作一下。
代码语言:javascript复制 state: {
count: 5
},
mutations: {
increment (state) {
// 变更状态
state.count
}
}
代码语言:javascript复制 mounted () {
setInterval(() => {
this.$store.commit('increment', {
remarks: '位于首页的计数器'
})
}, 5000)
}
可以看到,我们每次调用commit都能留下一条操作记录,操作记录里包含了我们触发的Mutation,触发的时间,触发之后的state,我们甚至还可以在其中留下一些‘登记信息’,以便我们不知道这个值是被哪个页面修改的时候进行调试,我们只要单击任意一条记录,便可查看该次触发的详细信息。
并且当我们点击这个小图标时,调试工具包括页面上引用的store实例状态将马上变回此次触发Mutation后store实例的状态,并且我们还可以随时点击最新的记录,以便回到最新的状态。
不要直接进行赋值操作
看,通过调用commit触发Mutation的方法对于我们的调试来说是不是如此之方便,那如果我们使用直接赋值的方式进行操作会怎么样呢?
代码语言:javascript复制 mounted () {
setInterval(() => {
this.$store.state.count
}, 5000)
}
虽然store实例里的值确实被修改了,但却没有留下任何操作记录,我们也无法在调试记录里看到最新的state的值,这对于我们的调试将十分不利,并且对我们的代码语义化也十分不利,它将变得更难以阅读、难以理解。
严格模式
如果你想在项目中仅通过Mutation去修改state里面的值,你可以开启严格模式。在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
开启严格模式,仅需在创建 store 的时候传入 strict: true
const store = new Vuex.Store({
// ...
strict: true
})
不要在发布环境下启用严格模式! 严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。
类似于插件,我们可以让构建工具来处理这种情况:
代码语言:javascript复制const store = new Vuex.Store({
// ...
strict: process.env.NODE_ENV !== 'production'
})
不要在Mutation中进行异步操作 当我们在Mutation中进行异步操作时,Vuex将无法知道我们此次的异步操作将在何时完成,也就无法在操作记录里留下正确的数据,这也就违背了Mutation设计的初衷,下面我们通过实操来看一下
代码语言:javascript复制 mutations: {
increment (state) {
// 变更状态
setTimeout(() => {
state.count
})
}
}
可以看到,虽然store实例中的数据可以跟着变化,但我们在每次修改内容的操作记录中却无法记录到正确的值,所以我们只能在Mutation中进行同步操作,那如果我们想要进行异步操作怎么办呢,比如我想把登录的操作放在Vuex中进行怎么办呢?Vuex针对异步操作,也贴心的为我们准备了下一个核心概念——action
actions
注意:为了防止误解,特注明,actions里的每一个函数都称为一个action,所以当我们说一个action的时候指的是action的一个函数
此处官方文档内容比较简单易懂,直接搬过来。
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
让我们来注册一个简单的 action:
代码语言:javascript复制const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count
}
},
actions: {
increment (context) {
setTimeout(() => {
context.commit('increment')
}, 2000)
}
}
})
代码语言:javascript复制this.$store.dispatch('increment')
由于Mutation和Action的触发方式不同,Mutation的名字可以和Action重名
由于action中可以进行任意异步操作,所以我们可以在action中进行异步操作(比如调用api接口)之后再调用commit调用Mutation。
action和Mutation不同的地方在于,Mutation的触发方式为commit
,action的触发方式为 dispatch
,另外Mutation的第一个参数为state,action的第一个参数为context(上下文对象),相同的是他们都可以用第二个参数接收用户传入的参数。
Vue基础知识巩固之全面了解Vuex,比官方更易懂(下)