在SPA单页面组件的开发中 Vue 的 vuex 和 React 的 Redux 都统称为同一状态管理,个人的理解是全局状态管理更合适;简单的理解就是你在 state 中定义了一个数据之后,你可以在所在项目中的任何一个组件里进行获取、进行修改,并且你的修改可以得到全局的响应变更。下面咱们一步一步地剖析下vuex的使用:
(1)首先要安装、使用 vuex
注意: 在vue 2.0+ 你的vue-cli项目中安装 vuex
npm install vuex --save
(2)在src文件目录下新建一个名为 store 的文件夹,为方便引入并在 store 文件夹里新建一个index.js,里面的内容如下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
// 创建 store 实例
export default new Vuex.Store()
(3)在 main.js里面 引入 store ,然后把 【store 的实例】注入所有的子组件,相当于全局注入,这样一来就可以在任何一个组件里面使用 this.$store 了:
import store from './store'
new Vue({
store,
render: h => h(App),
}).$mount('#app')
(4)在 store 文件夹下面新建一个 modules 文件夹,然后在modules 文件里面建立需要管理状态的 js 文件,既然要把不同部分的状态分开管理,那就要把它们给分成独立的状态文件了,
import account from './modules/account'
export default new Vuex.Store({
modules: {
account,
}
})
(5)在 store/modules.account.js 中,设置全局访问的 state 对象,要设置初始属性值,通过 db.get 方法获取缓存信息。
state: {
token: db.get('USER_TOKEN'),
user: db.get('USER'),
},
(6)通过上面步骤后,你已经可以用 this.$store.state.token 或 this.$store.state.user 在任何一个组件里面获取 token 和 user 定义的值了。
computed: {
token () {
return this.$store.state.account.token
},
user () {
return this.$store.state.account.user
}
},
(7)mutattions也是一个对象,这个对象里面可以放改变 state 的初始值的方法,具体的用法就是给里面的方法传入参数 state 或额外的参数,然后利用 vue 的双向数据驱动进行值的改变,同时 db.save 方法更新缓存信息,如下:
mutations: {
setToken (state, val) {
db.save('USER_TOKEN', val)
state.token = val
},
setUser (state, val) {
db.save('USER', val)
state.user = val
},
}
(8)其中的 namespaced:true 表示当你需要在别的文件里面使用( mapGetters、mapActions )时,里面的方法需要注明来自哪一个模块的方法:
import db from 'utils/localstorage'
export default {
namespaced: true,
state: {
...
},
mutations: {
...
}
}
(9)在组件中,如何获取 store 中的多个数据信息呢?当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。mapState
函数返回的是一个对象。通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed
属性。
import {mapState} from 'vuex'
computed: {
// 对象形式
...mapState({
token: state => state.account.token,
username: state => state.account.user.username,
}),
// 数组形式
...mapState([
'token',
'username'
])
},
(10)Getter 接受 state 作为其第一个参数,mapGetters
辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:
import { mapGetters } from 'vuex'
export default {
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([ 'doneTodosCount', 'anotherGetter' ])
mapGetters({
// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
}
}
(11)在组件里面,如何给 store 赋值呢?你可以在组件中使用 this.$store.commit('xxx')
提交 mutation,或者使用 mapMutations
辅助函数将组件中的 methods 映射为 store.commit
调用(需要在根节点注入 store
)。这里将把登录接口返回的数据存储到 Vuex 和浏览器的 localstorage 中。
import { mapMutations } from 'vuex';
methods: {
// store.commit 提交 mutation
onColorChange (values, colors) {
this.$store.commit('setting/setColor', colors)
},
// mapMutations 映射为 store.commit 调用
...mapMutations({
setToken: 'account/setToken',
setUser: 'account/setUser',
}),
saveLoginData (data) {
this.setToken(data.token)
this.setUser(data.user)
},
}
(12)在组件中使用 this.$store.dispatch('xxx')
分发 action,或者使用 mapActions
辅助函数将组件的 methods 映射为 store.dispatch
调用(需要先在根节点注入 store
):
import { mapActions } from 'vuex'
export default {
methods: {
updateToken() {
this.$store.dispatch('account/setToken', this.token); // account.js里actions里的setToken方法
},
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
登录流程小案例:
(1)登录点击事件:
// 登录操作
async handleSubmit (e) {
e.preventDefault()
const result = await this.$store.dispatch('Login', this.form)
},
(2)在 src/store/modules/passport.js 文件里,actions 中,添加接口请求,将结果 userInfo 使用 commit 提交 mutation,数据存储到 Vuex 的 state 中。
const passport = {
state: {
info: {},
},
mutations: {
SET_INFO: (state, info) => {
state.info = info
},
}
actions: {
// 登陆
async Login ({ commit }, userInfo) {
const { username, password } = userInfo
const resp = await api.login(username, password)
const { tokenInfo, userInfo } = resp.data
...
commit('SET_INFO', userInfo)
}
}
}
export default passport
(3)如何全局获取数据呢?
import { mapState } from 'vuex'
computed: {
...mapState: ({
userInfo: state => state.passport.info
})
}
mounted () {
console.log('mounted', this.userInfo) // 获取到存储在 Vuex 全局的数据状态
}
当然如果我们的登录是弹框的样子,有些路由未登录时,我们会检查登录态,详情请参考:使用 vue-router 全局守卫钩子函数,根据登录状态进行路由拦截以及滚动条回到页面顶部。当判断为未登录状态时,我们需要弹出登录框提示用户登录后,便可以操作。这是我们可以使用 vuex 全局状态管理来控制登录弹框的显示或隐藏。
import { mapMutations } from 'vuex'
methods: {
...mapMutations([
'setLoginVisible',
]),
// 登录点击事件
toLogin () {
// 或者使用 this.$store.commit('setLoginVisible', true)
this.setLoginVisible(true)
},
}
store 中如何配置呢?
// src/store/modules/user.js
const user = {
state: {
loginVisible: false, // 登录弹窗显示
},
mutations: {
setLoginVisible: (state, loginVisible) => {
state.loginVisible = loginVisible
},
}
}
export default user
当然登录组件挂载在主体架构下面的:
// 登录
export default {
name: 'Framework',
components: {
Login,
},
computed: {
...mapState({
loginVisible: state => state.user.loginVisible,
})
},
}
那么我们如何设置 Login 组件呢?
// src/views/User/Login/Login.vue
...
登录提交事件:
import { mapActions } from 'vuex'
...mapActions(['Login']),
async handleSubmit (e) {
e.preventDefault()
......
// 或者使用 await this.$store.dispatch('Login', this.form)
await this.Login(this.form)
this.setLoginVisible(false)
}
store 中 action 是异步函数,可以调用接口信息
// src/store/modules/user.js
import api from '@/http/api/user'
const user = {
actions: {
async Login ({ commit }, userInfo) {
const { username, password, marketId } = userInfo
try {
const resp = await api.login({
username,
password,
})
const { tokenInfo, userInfo } = resp.data
} catch (e) {
throw e
}
}
}
}
当然获取 vuex 中数据状态管理如下:
import { mapState } from 'vuex'
computed: {
...mapState({
userInfo: state => state.user.userInfo || {},
})
}
// 或者使用 const userInfo = this.$store.state.user.userInfo || {}
为啥我们可以使用 this.$store 来获取存储的全局数据信息呢?
在实例化挂载 Vue 节点时,引入 store 的相关文件信息。
import store from './store'
new Vue({
router,
store,
mounted () {},
render: h => h(App)
}).$mount('#app')
总结:
vuex分为state,getter,mutation,action四个模块,四个模块的作用如下:
(1)state:定义变量;
(2)getters:获取变量;
(3)mutations:同步执行对变量进行的操作;
(4)actions:异步执行对变量进行的操作;
(5)mapMutations/mapActions 只是把 mutation/action 函数绑定到methods里面,调里面的方法时正常传参数。
注意:映射都是映射到当前对象,使用时需要用 this 来调用。更多详细信息,请查看 Vuex 官网,参考案例:购物车