vuex主要是vue的状态管理,如果我们在项目中只通过传参、缓存等方式来同步data中的值,一旦项目变得大了页面多并且很复杂的时候就会变得很难维护和管理。vuex就把我们频繁使用的值进行集中管理,可以在整个项目中共同使用
-
state:存储状态(变量)。使用:
$sotre.state.xxx
-
getters:可以理解为state的计算属性。加工state成员给外界。使用:
$sotre.getters.functionName()
-
mutations:修改状态,并且是同步的。可以用来修改state中的状态。使用:
$store.commit(state,payload)
-
actions:异步操作。在组件中使用是
$store.dispath(context,payload)
- modules:store的子模块,为了开发大型项目,方便状态管理而使用的。
建议安装vue官方的调试工具插件:VueDevtools,调试起来很方便
1、安装vuex、初始化
安装vuex
npm i vuex -s
1.1、我的vue项目是用webpack搭建的,工程目录可能和别人有点不一样
1.2、根目录下新建store文件夹,里面再新建store.js并对vuex进行初始化
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
getters: {
},
mutations: {
},
actions: {
}
})
1.3、将vuex挂载到全局main.js中
import Vue from 'vue'
import store from './store'
import router from './router'
import { Button, ConfigProvider } from 'ant-design-vue'
import 'ant-design-vue/dist/antd.min.css'
import app from './src/App'
Vue.config.productionTip = false
Vue.use(Button)
Vue.use(ConfigProvider)
new Vue({
store,
router,
render: h => h(app)
}).$mount('#app')
2、State
我们可以把他理解为vue组件中的data和小程序中的globalData,在state里面定义我们想要共享的一些数据
用户登录之后后台返回给我们用户信息,然后我们把他存放到vuex的state中(这里为了方便就直接再state写入了,到时候可以用
this.$store.commit(state,payload)
来向state中注入共享的数据)
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
userInfo: {
userId: "1286507476287508480",
name: "测试姓名",
nickname: "pigs",
openId: "ogxJ55E2WqZ2-vyK2mlDtEXG28A4shvruybpohpsde",
portraitUrl: "https://proxy01.hnchengyue.com/image/portrait/1286507476253954048.jpg?v=4b487d14-9580-443b-9632-fa9615a2812b",
qrCode: "",
regNo: null,
sex: "男",
birthday: null,
employeeState: 1,
gmtCreate: null,
gmtModified: null,
},
commonInfo: 'commonInfo is a string'
},
getters: {
},
mutations: {
},
actions: {
}
})
在组件中这样使用
<template>
<div>
login页面
<div>这是VueX中的信息:{{$store.state.userInfo.name}}</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import httpUtil from '../utils/request'
export default {
data () {
return {
}
},
mounted() {
console.log('这是vuex中的userInfo:',this.$store.state.userInfo)
}
}
</script>
在页面中也可以获取到vuex中的用户信息
VueDevtools中的数据也是正确的
当我们的组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余,为了解决这个问题,我们可以使用mapState的辅助函数来帮助我们生成计算属性
3、Getters
-
如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。
-
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter
的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。 -
getters接收2个参数,第一个参数是:当前VueX对象中的状态对象;第二个参数是:当前getters对象,用于将getters下的其他getter拿来用
现在我们除了userInfo又加了一条共享的数据:commonInfo,这条数据我们就可以通过getters来访问到
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
userInfo: {
userId: "1286507476287508480",
name: "测试姓名",
nickname: "pigs",
openId: "ogxJ55E2WqZ2-vyK2mlDtEXG28A4shvruybpohpsde",
portraitUrl: "https://proxy01.hnchengyue.com/image/portrait/1286507476253954048.jpg?v=4b487d14-9580-443b-9632-fa9615a2812b",
qrCode: "",
regNo: null,
sex: "男",
birthday: null,
employeeState: 1,
gmtCreate: null,
gmtModified: null,
},
commonInfo: 'commonInfo is a string'
},
getters: {
getUserInfo(state) {
return state.userInfo;
},
getCommonInfo(state,payload){
return `共享的数据:${state.commonInfo}`
}
}
})
在组件中这样使用:通过
this.$store.getters.xxx
访问
<template>
<div>
login页面
<div>这是VueX中的信息:{{$store.state.userInfo.name}}</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import httpUtil from '../utils/request'
export default {
data () {
return {
}
},
mounted() {
console.log('这是vuex中的userInfo:',this.$store.state.userInfo)
//或者console.log('这是vuex中的userInfo:',this.$store.getters.getUserInfo)
}
}
</script>
Getters中的第二个参数可以将getters下的其他getter拿来用
getters:{
getUserInfo(state){
return `姓名:${state.userInfo.name}`
},
getCommonInfo(state,payload){
return `姓名:${payload.getUserInfo};数据${state.commonInfo}`
}
}
4、Mutation
如果有个用户修改了他的姓名,现在我们要同步state中的数据就可以用Mutation来进行操作,也可以用来删除、添加
注意:
- 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation
- mutation是一个同步的操作
每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数
(handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state(当前VueX对象中的state)
作为第一个参数;payload(该方法在被调用时传递参数使用的)作为第二个参数
在login页面中有一个按钮,现在我们想点击按钮的时候更改userInfo的name字段
<template>
<div>login页面
<div>这是VueX中的信息:{{$store.state.userInfo.name}}</div>
<a-button type="primary" @click="onClickBtn">
Primary
</a-button>
</div>
</template>
<script>
export default {
data () {
return {
}
},
methods: {
onClickBtn(){
let userInfo = this.$store.state.userInfo
userInfo.name = '新的测试名字'
this.$store.commit('setUserInfo',{
userInfo: userInfo
})
console.log('修改后的state数据:',this.$store.state.userInfo)
}
}
}
</script>
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
userInfo: {
userId: "1286507476287508480",
name: "测试姓名",
nickname: "pigs",
openId: "ogxJ55E2WqZ2-vyK2mlDtEXG28A4shvruybpohpsde",
portraitUrl: "https://proxy01.hnchengyue.com/image/portrait/1286507476253954048.jpg?v=4b487d14-9580-443b-9632-fa9615a2812b",
qrCode: "",
regNo: null,
sex: "男",
birthday: null,
employeeState: 1,
gmtCreate: null,
gmtModified: null,
},
userId: '1212121',
commonInfo: 'commonInfo is a string'
},
getters: {
getUserInfo(state) {
return state.userInfo;
},
getCommonInfo(state,payload){
console.log("getters中的payload数据:",payload)
// return `姓名:${payload.getUserInfo};数据${state.commonInfo}`
return `共享的数据:${state.commonInfo}`
}
},
mutations: {
setUserInfo(state, payload) {
console.log('mutations中的',payload.userInfo)
state.userInfo = payload.userInfo
}
}
})
新增数据
Vue.set(state.userInfo,"age",15)
删除数据
Vue.delete(state.userInfo,'openId')
5、Actions
Action 类似于 mutation,不同在于:
Action 提交的是 mutation,而不是直接变更状态
Action 可以包含任意异步操作
想要异步地更改状态,就需要使用action。action并不直接改变state,而是发起mutation。
Actions中的方法有两个默认参数
-
context 上下文(相当于箭头函数中的this)对象
-
payload 挂载参数
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
userInfo: {
userId: "1286507476287508480",
name: "测试姓名",
nickname: "pigs",
openId: "ogxJ55E2WqZ2-vyK2mlDtEXG28A4shvruybpohpsde",
portraitUrl: "https://proxy01.hnchengyue.com/image/portrait/1286507476253954048.jpg?v=4b487d14-9580-443b-9632-fa9615a2812b",
qrCode: "",
regNo: null,
sex: "男",
birthday: null,
employeeState: 1,
gmtCreate: null,
gmtModified: null,
},
userId: '1212121',
commonInfo: 'commonInfo is a string'
},
getters: {
getUserInfo(state) {
return state.userInfo;
},
getCommonInfo(state,payload){
console.log("getters中的payload数据:",payload)
// return `姓名:${payload.getUserInfo};数据${state.commonInfo}`
return `共享的数据:${state.commonInfo}`
}
},
mutations: {
setUserInfo(state, payload) {
state.userInfo = payload.userInfo
}
},
actions: {
aFun(context, payload){
console.log(context)
console.log('actions中的',payload.userInfo)
setTimeout(() => {
context.commit('setUserInfo', payload)
}, 2000)
}
}
})
在组件中这样使用
<template>
<div>login页面
<div>这是VueX中的信息:{{$store.state.userInfo.name}}</div>
<a-button type="primary" @click="onClickBtn">
Primary
</a-button>
</div>
</template>
<script>
import { mapState } from 'vuex'
import httpUtil from '../utils/request'
export default {
data () {
return {
}
},
mounted() {
this.$store.dispatch('aFun',{
userInfo: '哈哈哈哈哈'
})
},
}
</script>
<style scoped>
</style>
由于Actions里面是异步的操作,我们可以用Promise或者async、await来封装一下
Promise方式
actions: {
aFun(context, payload){
console.log(context)
console.log('actions中的',payload.userInfo)
return new Promise((resolve, reject) => {
context.commit('setUserInfo', payload)
resolve()
})
}
}
async、await方式
假设 getData() 返回的是 Promise
actions: {
async aFun ({ commit }) {
commit('setUserInfo', await getData())
}
}
6、Module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
models: {
modelA: {
state:{},
getters:{},
actions: {}
},
modelB: {
state:{},
getters:{},
actions: {}
}
}
在对应的页面中调用modelA的状态
this.$store.state.modelA
提交或者dispatch某个方法和以前一样,会自动执行所有模块内的对应type的方法
this.$store.commit('setUserInfo')
this.$store.dispatch('aFun')
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
models:{
modelA:{
state:{key:5},
mutations:{
editKey(state){
state.key = 9
}
}
}
}
同样,对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState
models:{
modelA:{
state:{count:5},
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
}
getters中方法的第三个参数是根节点状态
models:{
modelA:{
state:{key:5},
getters:{
getKeyCount(state,getter,rootState){
return rootState.key + state.key
}
},
....
}
}
**
命名空间
**
默认情况下,模块内部的 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']
}
}
}
}
}
})
7、辅助函数:mapState、mapMutations、mapGetters等等等
mapState辅助函数
注意:mapState的属性的时候,一定要和state的属性值相对应,也就是说 state中定义的属性值叫add,那么mapState就叫add,如果我们改成add2的话,就获取不到add的值了,并且add2的值也是 undefined
<script>
import { mapState } from 'vuex';
export default {
data() {
return {
}
},
methods: {
},
computed: {
...mapState({
add: state => state.add,
counts: state => state.counts
})
},
mounted() {
console.log(this.add)
console.log(this.counts)
}
}
</script>
mapGetters 辅助函数
mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'getUserInfo',
'getCommonInfo',
// ...
])
}
}
mapActions 辅助函数
import { mapActions } from 'vuex'
export default {
// ...
computed: {
// mapActions 使用方法一 将 aFun() 映射为 this.$store.dispatch('aFun')
...mapActions(['aFun')
//第二种方法
...mapActions({
'aFun': 'aFun'
})
}
mapMutations辅助函数
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'setUserInfo', // 将 `this.setUserInfo()` 映射为 `this.$store.commit('setUserInfo')`
'setCommonInfo',
]),
}
}
8、规范目录结构
- 应用层级的状态应该集中到单个 store 对象中。
- 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
- 异步逻辑都应该封装到 action 里面。
如果 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。
store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
9、利用vuex实现更换主题色小案例
在某些网站或者app中应该都见过,点击某个色块的时候,全局的某个地方的背景颜色都会变成一样的,就像下方的效果。话不多说上代码
首先俺们在vuex的state中定义好默认的主题色,很多时候都是从后台获取全局的配置,代码里面就简单的写死了,方便测试,getters和mutations也提前写好
案例已uniapp框架为主,其实和vue都一样了
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const extConfig = uni.getExtConfigSync ? uni.getExtConfigSync() : {};
const store = new Vuex.Store({
state: {
shopName:'首页',
phone:extConfig.phone,
appid: extConfig.appid,
token: extConfig.token,
themeBgColor: extConfig.themeBgColor || '#00b000',//全局主题颜色
},
getters: {
getThemeBgColor: (state, theme) => {
console.log(state);
console.log(theme);
return state.themeBgColor
}
},
mutations: {
setMerchant(merchant){
state.merchant = merchant
},
setThemeBgColor: (state, theme) => {
console.log(state, theme);
state.themeBgColor = theme.themeBgColor
}
}
})
export default store
随后在main.js中引用vuex
我的页面进行更换主题色
<script>
var that;
import { mapGetters } from 'vuex'
import uniPopup from '../../components/uni-popup/uni-popup.vue'
export default {
data() {
return {
globalTheme: '',
// 主题色
themeList: [
{ text: 'Dim', color: '#41555d' },
{ text: 'Grey', color: '#808080' },
{ text: 'Dark', color: '#161a23' },
{ text: 'Water', color: '#88ada6' },
{ text: 'Blush', color: '#f9906f' },
{ text: 'Orange', color: '#ff8936' },
{ text: 'Indigo', color: '#27547d' },
{ text: 'Volcano', color: '#fa541c' },
{ text: 'Verdant', color: '#519a73' },
{ text: 'PinkGold', color: '#f2be45' },
{ text: 'Verdigris', color: '#549688' },
{ text: 'DeepBlack', color: '#3d3b4f' },
{ text: 'DarkGreen', color: '#75878a' },
{ text: 'IndigoBlue', color: '#177cb0' },
{ text: 'BluishGreen', color: '#48c0a3' },
]
}
},
components: { uniPopup },
computed: {
getGlobalTheme() {
return this.$store.state.themeBgColor
}
},
watch: {
getGlobalTheme(val){
console.log(val);
this.globalTheme = val
this.$store.commit('setThemeBgColor',{
themeBgColor: val
})
uni.setNavigationBarColor({
frontColor: '#ffffff',
backgroundColor: val,
})
}
},
methods: {
//打开主题色弹出层
openThemeModal(){
this.$refs.popup.open()
},
//更换主题颜色
onChangeTheme(item){
this.themeBgColor = item.color
this.$store.commit('setThemeBgColor',{
themeBgColor: item.color
})
this.$refs.popup.close()
}
}
}
</script>
更换主题色后需要监听vuex中state值的变化并给navbar设置我们选中的颜色。利用
computed
和
watch
来监听
-
首先我们在computed中写一个计算属性,返回状态管理中state的themeBgColor
-
然后在watch中监听这个计算属性的变化,对其重新赋值。
在购物车页面获取修改后的主题色
建议放在onShow生命周期里面
onShow() {
console.log(this.$store.state.themeBgColor);
uni.setNavigationBarColor({
frontColor: '#ffffff',
backgroundColor: this.$store.state.themeBgColor,
})
},
computed: {
getGlobalTheme() {
return this.$store.state.themeBgColor
}
},
watch: {
getGlobalTheme(val){
console.log("购物车:",val);
this.globalTheme = val
// uni.setNavigationBarColor({
// frontColor: '#ffffff',
// backgroundColor: val,
// })
}
},