在进行vue2项目开发的时候,我们有时会希望某些数据能够全局共享这时候vuex就能够帮到我们
vuex可以将数据集中起来管理,我把vuex叫做一个仓库。在vue2中通常使用vuex3,在vue3中通常适合用vuex4,所以这里我们使用的是vuex3。
创建store仓库
第一步,在vue2项目中下载vuex3,如果用cli自定义项目,可以直接勾选vuex,他会自动帮你下载导入
npm add vuex@3
第二步,在src目录下新建一个store目录,并在store目录下新建index.js文件
第三步,在index.js中引入Vue和Vuex,然后Vue.use(Vuex)(这些步骤是必要的),然后再导出一个新建的Vuex.store对象给main.js引入挂载,在Vuex.store对象中有许多方法,这里暂时不着急,先写上,后面再说
在store下的index.js中:
//这里面存放的就是vuex的核心代码
import Vue from 'vue'
import Vuex from 'vuex'
//插件安装
Vue.use(Vuex)
//创建仓库
export default new Vuex.Store({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
在main.js中:
import Vue from 'vue'
import App from './App.vue'
//引入了vuex
import store from './store'
Vue.config.productionTip = false
new Vue({
//将vuex挂载
store,
render: h => h(App)
}).$mount('#app')
完成这几步,我们就已经创建好了一个vuex所管理的store全局数据仓库了,这时候我们还只是创建了一个空的仓库,但是还没有数据,在vuex中所有共享的数据都要统一放在仓库的State中存储。
State和data相似,只不过State是所有组件共享的数据,而data是某个组件自己的数据。这里我在仓库中添加了一个count数据,它的值为1。仓库可以提供很多变量,并不只是count
//这里面存放的就是vuex的核心代码
import Vue from 'vue'
import Vuex from 'vuex'
//插件安装
Vue.use(Vuex)
//创建仓库
export default new Vuex.Store({
state: {
count:1,
title:'Hello World'
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
访问数据(state)
给仓库添加了数据之后就要来使用数据了,使用数据有两种方法:①通过store直接访问②通过辅助函数来访问。这里先来看第一种,通过store直接访问
1、在模板中使用
{{ $store.state.count }}
2、在组件逻辑中(即在.vue文件的script中)
this.$store.state.count
3、在js模块中
store.state.count
我们用辅助函数来访问,这里需要用到vuex中的mapStates辅助函数,能够帮助我们把store中的属性自动映射到组件的计算属性中,这里我们可以不用纠结于映射是什么概念,我们只要知道通过mapStates辅助函数可以让我们在组件模板中使用vuex仓库的数据时,不用再写原生代码(例如$store.state.count)那么长的英文了
在组件中使用辅助函数时,有两个步骤
1、引入mapStates
import {mapStates} from 'vuex'
2、在计算属性中使用...展开运算符映射
computed:{
...mapState(['count','title'])
}
因为在开发中的仓库会有很多条数据,所以在扩展mapStates中用数组来包裹你想要在这个组件中使用的数据名称
这样我们可以在组件模板中直接使用数据名称也是没有问题的
{{count}}
//1
{{title}}
//Hello World
修改数据(mumations)
现在我们可以访问到数据了,我们还想要修改store仓库中的数据,这时候又有一个新的概念mutations。
在了解mutations之前,我们要遵循vuex的单项数据流,即组件可以访问store仓库中的数据,不能直接修改store仓库中的数据,但是我们可以借助mutations间接修改store仓库中的数据
在store仓库mutations中添加一个给state中的count变量+1的方法
//这里面存放的就是vuex的核心代码
import Vue from 'vue'
import Vuex from 'vuex'
//插件安装
Vue.use(Vuex)
//创建仓库
export default new Vuex.Store({
state: {
count:1,
title:'Hello World'
},
getters: {
},
mutations: {
//函数的第一个参数是当前store的state属性
addCount(state){
state.count++
}
},
actions: {
},
modules: {
}
})
这时我们在组件的方法中提交调用mutations,就会使得store中state属性的count值+1
methods:{
handleCount(){
this.$store.commit('addCount')
}
//count+1
}
但此时如果我们又有其他的需求,我想让count+5,或者count+10怎么办?这时候我们就需要传参,在addCount中第一个参数为state,我们可以设置第二个参数来改变count的值
//这里面存放的就是vuex的核心代码
import Vue from 'vue'
import Vuex from 'vuex'
//插件安装
Vue.use(Vuex)
//创建仓库
export default new Vuex.Store({
state: {
count:1,
title:'Hello World'
},
getters: {
},
mutations: {
//函数的第一个参数是当前store的state属性
addCount(state,n){
state.count+=n
}
},
actions: {
},
modules: {
}
})
这时候我们在组件中使用commit,也传入值,就能让这个方法多次复用了。如果想传入多个值的话,可以用对象把值给包裹起来再传参
methods:{
handleCount(){
this.$store.commit('addCount',5)
}
//count+5
}
这个时候又遇到难题了,我想在input框里实现实时输入,实时更新,但是在组件中不能直接修改store仓库中的值,所以不能使用v-mode,我们要遵循vuex的单项数据流。我们反向思考,将v-model拆分为:value和@input的组合,用:value来渲染,用@input来监听输入的内容
<template>
<div id="app">
<h1>根组件 - {{ title }} - 哈哈</h1>
<input :value="count" @input="handleInput" type="text">
</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
computed:{
...mapState(['count','title'])
},
methods: {
handleInput(e){
//实时获取输入框的值
console.log(e.target.value);
this.$store.commit('addCount',e.target.value)
}
},
}
</script>
<style lang="less">
#app{
width: 600px;
margin:20px auto;
border:3px solid #ccc;
border-radius: 3px;
padding: 10px;
}
</style>
辅助函数mapMutations
mapMutations和mapStates很像,它是把位于mutations中的方法提取了出来,映射打到组件methods中
<template>
<div id="app">
<h1>根组件 - {{ title }} - 哈哈</h1>
<input :value="count" @input="handleInput" type="text">
</div>
</template>
<script>
//引入mapMutations
import {mapState,mapMutations} from 'vuex'
export default {
computed:{
...mapState(['count','title'])
},
methods: {
//扩展映射addCount方法
...mapMutations(['addCount'])
handleInput(e){
//实时获取输入框的值
console.log(e.target.value);
//this.$store.commit('addCount',e.target.value)
//直接调用
this.addCount(e.target.value)
}
},
}
</script>
<style lang="less">
#app{
width: 600px;
margin:20px auto;
border:3px solid #ccc;
border-radius: 3px;
padding: 10px;
}
</style>
异步修改数据或调用数据(actions)
actions在store中和mutations一样,也是用来修改state中的值的,但是actions不同点在于它使用异步的方式来处理数据的,这时候就有疑惑了,mutations中不能做这些操作吗?没错,mutations必须是同步的(便于监测数据变化,记录调试),如果以后有数据请求来改变state中的数据那么只能使用actions来异步改变数据,虽然是在actions中处理异步,处理完异步之后,还是需要通过mutations来修改数据
需求:一秒钟后,修改state的count为某个数
//这里面存放的就是vuex的核心代码
import Vue from 'vue'
import Vuex from 'vuex'
//插件安装
Vue.use(Vuex)
//创建仓库
export default new Vuex.Store({
state: {
count:1,
title:'Hello World'
},
getters: {
},
mutations: {
//函数的第一个参数是当前store的state属性
changeCount(state,n){
state.count = n
}
},
actions: {
//这里的context可以把它看作store本身
setAsyncCount(context,num){
//一秒钟后,给一个数,去修改count
setTimeOut(()=>{
context.commit('changeCount',num)
},1000)
}
},
modules: {
}
})
在组件中用dispatch调用actions方法。本质上是actions先给你处理了异步,再把剩下的丢给mutations处理
this.$store.dispatch('setAsyncCount',200)
辅助函数mapActions
mapActions是把位于actions中的方法提取了出来,映射到组件methods中
<template>
<div id="app">
<h1>根组件 - {{ title }} - 哈哈</h1>
<input :value="count" @input="handleInput" type="text">
//直接调用actions方法进行异步修改
<button @click="setAsyncCount(666)">修改666</button>
</div>
</template>
<script>
//引入mapActions
import {mapState,mapMutations,mapActions} from 'vuex'
export default {
computed:{
...mapState(['count','title'])
},
methods: {
//扩展映射addCount方法
...mapMutations(['addCount']),
//扩展映射setAsyncCount方法
...mapActions(['setAsyncCount']),
handleInput(e){
//实时获取输入框的值
console.log(e.target.value);
//this.$store.commit('addCount',e.target.value)
//直接调用
this.addCount(e.target.value)
}
},
}
</script>
<style lang="less">
#app{
width: 600px;
margin:20px auto;
border:3px solid #ccc;
border-radius: 3px;
padding: 10px;
}
</style>
辅助函数getters
除了state之外,有时我们还需要从state中派生出一些状态,这些状态时依赖state的,此时会用到getters。例如:state中定义了list,为1-10的数组,组件中,需要显示所有大于5的数据
state:{
list:[1,2,3,4,5,6,7,8,9,10]
}
定义getters
getters:{
//注意:
//(1)getters函数的第一个参数必须是state
//(2)getters函数必须有返回值
filterList(state){
return state.list.filter(item => item > 5)
}
//形如计算属性
}
定义完成后,有两种访问getters的方式
①通过store访问getters
//在模板语法中
{{$store.getters.filterList}}
//在script中
this.$store.getters.filterList
②通过辅助函数mapGetters映射
//同样也要引入mapGetters
computed:{
...mapGetters(['filterList'])
}
//模板语法和script中都可以直接使用,就相当于组件中的计算属性
{{ filterList }}
模块modules(进阶语法)
由于vuex使用单一状态树,项目的所有数据会集中到一个js文件。当项目变得非常复杂时,store仓库就会变得相当臃肿,难以维护。这时候我们可以使用modules,创建多个子仓库来进行模模块化管理,第一步在store文件夹下新建一个modules文件夹,再在modules文件夹下再新建子仓库,例如用户(user.js)的数据作为一个子仓库来管理,设置(setting.js)作为一个子仓库来管理。
将来在实际开发项目中会有非常非常多的模块,而每一个js文件就是一个模块,并且每一个模块都有自己的核心概念(例如state,mutations,actions)
先来看看怎么设置子模块,这里我设置了一个user.js模块,子模块中有state、mumations、actions和getters,都用对象来表达。最后再做一件事,将所有对象导出,相当于导出了一个子模块
//user模块
const state = {
userInfo:{
name:'张三',
age:18
},
score:80
}
const mutations = {
updataUser(state,newUser){
state.userInfo = newUser
}
}
const actions = {}
const getters = {}
export default {
state,
mutations,
actions,
getters
}
然后再在store文件夹下的index.js文件下进行导入,在modules中进行应用,这里的user是简写,实际上是user:user
import user from './modules/user'
const store = new Vuex.store({
modules:{
user
}
})
到这一步,尽管已经分模块了,已经模块化了但是子模块的状态,还是会挂载到根级别(index.js)的state中,属性名就是模块名
想要拿到子模块的数据(state):
①直接通过模块名访问 $store.state.模块名.xxx
{{ $store.state.user.userInfo.name }}
//张三
②通过mapState映射
默认根级别的映射 mapState(['xxx']) - 详情可见上方的访问数据
computed:{
..mapState(['user'])
//相当于把子模块的整个state进行了映射,想要访问子模块user.js的数据直接user.xxx
}
另一种:子模块的映射 mapState('模块名',['xxx']) - 需要开启命名空间
使用这个方法可以限定mapState只能访问某个模块的数据,如果在开发中有多个模块有相同的数据,那么这个方法可以很好的避免重复,那么这个方法需要开启命名空间,什么是命名空间?其实很简单,在子模块的导出项田间一个namespaced:true就可以了
//user模块
const state = {
userInfo:{
name:'张三',
age:18
},
score:80
}
const mutations = {
updataUser(state,newUser){
state.userInfo = newUser
}
}
const actions = {}
const getters = {}
export default {
//开启命名空间
namespaced:true
state,
mutations,
actions,
getters
}
在组件中使用
computed:{
...mapState('user',['userInfo','score'])
//多个..mapState可以使用多次,不会报错正常写法
//'user'为限定的子组件名称,['userInfo']为限定子组件的数据,可以写多个
}
使用子模块的getters访问语句
①直接通过模块名访问$store.getters['模块名/xxx']
②通过mapGetters映射
子模块的映射mapGetters('模块名',['xxx']) - 需要开启命名空间
我先在user.js子模块中添加一个getters方法
//user模块
const state = {
userInfo:{
name:'zs',
age:18
},
score:80
}
const mutations = {
updataUser(state,newUser){
state.userInfo = newUser
}
}
const actions = {}
const getters = {
//分模块后,state指代子模块的state
//这里将userInfo中的姓名转为大写
UpperCaseName(state){
return state.userInfo.name.toUpperCase()
}
}
export default {
//开启命名空间
namespaced:true
state,
mutations,
actions,
getters
}
在组件中通过原生的方法获取,也就是直接通过模块名访问
{{ $store.getters['user'/UpperCaseName] }}
//ZS
//成功将小写的zs转为大写的ZS
在组件中通过映射的方法实现(原理和mapState一样):
computed:{
//在组件中映射
...mapGetters('user',['UpperCaseName'])
}
//在模板中直接使用
{{ UpperCaseName }}
//ZS
调用子模块中的mutations修改子模块的数据
调用方式:
①直接通过store调用 $store.commit('模块名/xxx',额外参数)
//组件中
methods:{
xxx(){
this.$store.commit('模块名/actions方法名',参数)
}
}
②通过mapMutations映射
子模块的映射mapMutations('模块名',['xxx']) - 需要开启命名空间
methods:{
...mapActions('user',['xxx'])
}
//xxx为子模块中的方法名
<button @click="xxx(参数)">按钮</button>
//可以在组件中直接使用