vuex的基本内容
一、Vuex的理解
1、Vuex是什么?
官方标准: Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
个人理解: Vuex就是一个专门用来全局管理vue组件之间通信的管理工具。比方说,你在state当中定义一个属性,那么你在所有的组件当中就都可以通过指定的方式、方法来获取并修改这个值,并且修改的这个值会在全局当中响应变更。
2、Vuex是用来干什么的?
大家试想一下,如果我们在项目开发中频繁的使用组件传值的方式来同步data的值,一旦项目变得非常庞大,管理和维护这些值就会变得异常的麻烦。因此,Vue提供了一个统一管理工具——Vuex,Vuex就是专门用来统一管理这些被多个组件频繁使用的值。
3、为什么要使用Vuex?
VueX状态自管理应用包含以下几个部分:
- state(状态): 驱动应用的数据源。
- view(视图): 以声明的方式将state(状态)映射到视图上。
- actions(操作): 响应在view(视图)上的用户输入导致的状态变化。
我们都知道Vue的理念就是单页面应用和单项数据流:
但是,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏。纠其主要原因有如下两点:
- 多个视图依赖于同一状态。
- 来自不同视图的行为需要变更同一状态。
对于第一点,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。对于第二点,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。
因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!
通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态间的独立性,我们的代码将会变得更结构化且易维护。
这就是 Vuex 背后的基本思想,与其他模式不同的是,Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。
4、什么情况下才去使用vuex?
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。
二、Vuex的核心内容
1、State
理解: 用来管理和存储vue中的属性(存放状态)
如何获取State状态?
Vuex 通过 Vue 的插件系统将 store 实例从根组件中“注入”到所有的子组件里。且子组件能通过 this.$store 访问到store实例。
//main.js
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app');
//example.vue
<template>
<div id="container">
<span>{{count}}</span>
</div>
</template>
<script>
export default {
name: "example",
data() {
return {}
},
computed:{
time(){
return this.$store.state.count
}
},
methods: {}
}
</script>
<style lang="scss" scoped>
#container {
width: 100%;
height: 100%;
}
</style>
2、Mutation
理解: 更改 Vuex 中store状态的唯一方法,是同步,且必须是同步函数!(state成员操作)
如何定义一个mutation?
每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。
const store = createStore({
state: {
count: 1
},
mutations: {
//变更状态的回调函数,increment为一个字符串的事件类型(type)
increment (state) {
// 变更状态的操作
state.count++;
}
}
})
如何调用这个mutation?
我们并不能直接就调用这个 mutation 处理函数,而是应该以其相应的 字符串的事件类型(type) 调用 store.commit 方法。简单讲就是要传入其对应的函数方法的方法名,不然谁知道你要调用的是哪一个。。。
this.$store.commit('increment')
提交载荷(Payload), 即传入额外参数
此外,我们还可以向 this.$store.commit 传入额外的参数,即 mutation 的载荷(payload)
const store = createStore({
state: {
count: 1
},
mutations: {
//变更状态的回调函数,increment为一个字符串的事件类型(type)
increment (state,n) {
// 变更状态的操作
state.count += n;
}
}
})
this.$store.commit('increment',10)
载荷以对象的方式传入
const store = createStore({
state: {
count: 1
},
mutations: {
//变更状态的回调函数,increment为一个字符串的事件类型(type)
increment (state,payload) {
// 变更状态的操作
state.count += payload.n;
}
}
})
this.$store.commit('increment',{
n: 10
})
this.$store.commit 提交的两个参数以对象的方式传入
const store = createStore({
state: {
count: 1
},
mutations: {
//变更状态的回调函数,increment为一个字符串的事件类型(type)
increment (state,payload) {
// 变更状态的操作
state.count += payload.n;
}
}
})
this.$store.commit({
type:'increment',
n: 10
})
使用常量替代 Mutation 事件类型
const store = createStore({
state: {
count: 1
},
mutations: {
//变更状态的回调函数,increment为一个字符串的事件类型(type)
SET_INCREMENT: (state,payload)=> {
// 变更状态的操作
state.count += payload.n;
}
}
})
this.$store.commit({
type:'increment',
n: 10
})
3、Getter
理解: 相当于State的计算属性(加工state成员给外界)
为什么要用getter?
因为有时候我们需要对某一些状态进行特殊处理,比如过滤计算,正常情况下,我们是在组件中拿到这个状态然后做处理。
computed: {
handleCount () {
return this.$store.state.todos.filter((todo)=>{ return todo.done }).length
//return this.$store.state.todos.filter(todo=>todo.done).length //简写
}
}
但是,这种做法如果放到多个需要用到这个属性的组件中,那么我们要么就是复制这个函数方法,或者封装一个共享的函数方法,然后在多个地方导入它,无论哪一种方法,是不是都觉得很麻烦?而且,你每一个地方调用一次,它就得重新过滤计算一次。为此,getter就能帮我们解决这个问题,因为getter就相当于state状态的计算属性,可以直接在getter中计算好,然后再将这个加工完成的state成员抛给外界,然后你在多处地方用到这个状态的时候,直接store.getter.handleCount.length就可以了。
const store = createStore({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: (state) => {
return state.todos.filter(todo => todo.done)
}
}
})
computed: {
handleCount () {
return this.$store.getters.doneTodos.length
}
}
注意: getter 接受 state 作为其第一个参数,也可以接受其他 getter 作为第二个参数,且getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的。
4、Action
理解: 像一个装饰器,包裹mutations,使之可以异步操作。也可以处理同步的操作。(异步操作)
不同点:
action 类似于 mutation,但是不同于mutation,action 提交的是 mutation,而不是直接变更状态。
action 可以包含任意异步操作。
举例:
const store = createStore({
state: {
count: 0
},
mutations: {
incrementOne (state) {
state.count ++;
}
incrementTwo (state,n) {
state.count += n.n;
}
},
actions: {
incrementA (context) {
setTimeout(() => {
context.commit('incrementOne')
}, 1000)
},
//context.commit提交、context.state、context.getter获取state 和 getters
incrementB (context,n) {
setTimeout(() => {
context.commit('incrementTwo',{n: n.n})
}, 1000)
},
//参数解构,简化代码
incrementB ({commit},n) {
setTimeout(() => {
commit('incrementTwo',{n: n.n})
}, 1000)
}
}
})
action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,但是它并不是store实例本身,大家不要搞混了,等我们讲到modules的时候会详细介绍。因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
触发方式:
store.dispatch('incrementA')
store.dispatch('incrementB',{n:10})//以载荷形式分发
store.dispatch({type:'incrementB',n:10})//以对象形式分发
//如果有设置模块化,记得加store.dispatch('(模块的文件名)/incrementA')
组合使用:以登录为例
const actions = {
login({commit}, userInfo) {
return new Promise((resolve, reject) => {
login(userInfo).then(response => {
commit('SET_TOKEN', response.cookieMap)
commit('SET_USERINFO', response)
// 保存用户信息
setUserInfo(response);
resolve(response);
}).catch(error => {
//console.error('error', error);
reject(error);
})
})
}
}
store.dispatch('login',userInfo).then(() => {
// ...
})
5、Module
理解: 设置模块,让每一个模块都拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。(模块化Vuex)
例子
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const store = createStore({
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
}
}
}
同样,对于模块内部的 action,局部状态通过 context.state 暴露出来,对于模块内部的 getter,根节点状态会作为第三个参数context.rootState暴露出来。
const moduleA = {
// ...
actions: {
//这里直接用参数解构
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
},
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
6、辅助函数
理解: 以上,我们仅仅只是了解了State、Getters、Mutations、Actions、Modules等的概念,在实际使用过程中,随着业务功能的逐渐增加,会出现很多个状态。当一个组件需要多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。因此,为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,帮你减少工作量。
①、mapState
理解: mapState 函数返回的是一个对象。如果我们想将它与局部计算属性混合使用,通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。但是自从有了对象展开运算符,我们可以极大地简化写法。
import {mapState} from 'vuex';
export default {
// ...
computed: {
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
// ...
})
}
}
注意: 如果你是想要使用mapState混入不同模块中的状态,那么用以下方式,且要记住,如果你data中定义了跟计算属性计算的变量名一样,那这时候会报错,vue 就只会识别data中定义的变量。并且,mapState一般写在计算属性computed当中。
import {mapState} from 'vuex';
export default {
// ...
computed: {
...mapState({
'test':state => state.user.test//user模块
})
}
}
②、mapGetters
理解: mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性中。
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'handleCount',
'anotherGetter',
// ...
])
}
}
如果你想将一个 getter 属性另取一个名字,则可以使用对象形式。并且,mapGetters一般写在计算属性computed当中。
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
...mapGetters({
// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
}
}
③、mapMutations
理解: 使用mapMutations辅助函数将组件中的methods映射为store.commit调用。简单理解就是把mutations中的方法映射到methods当中,这样你就可以直接像methods中的方法一样,通过this.XXX调用该方法。
import { mapMutations} from 'vuex'
export default {
// ...
methods:{
//将this.tips映射成this.$store.commit('tips')
...mapMutations(['tips'])
}
}
注意: 如果你想用mapMutations映射到不同模块的store.commit调用,那么用以下的方法。并且,mapMutations一般写在methods当中。
import { mapMutations} from 'vuex'
export default {
// ...
methods:{
//将this.tips映射成this.$store.commit('tips'),并且是在user模块中
...mapMutations('user',['tips'])
}
}
④、mapAction
理解: 使用mapActions辅助函数将组件的methods映射成store.dispatch调用。简单理解就是把actions中的方法映射到methods当中,这样你就可以直接像methods中的方法一样,通过this.XXX调用该方法。
import { mapActions } from 'vuex'
export default {
// ...
methods:{
//将this.tips映射成this.$store.dispatch('tips')
...mapActions(['tips'])
}
}
注意: 如果你想用mapActions映射到不同模块的store.dispatch调用,那么用以下的方法。并且,mapActions一般写在methods当中。
import { mapAction} from 'vuex'
export default {
// ...
methods:{
//将this.tips映射成this.$store.dispatch('tips'),并且是在user模块中
...mapActions('user',['tips'])
}
}
7、命名空间
理解: 命名空间(namespaced)主要用来解决不同模块命名冲突问题。当modules中导入的组件过多,并且有相同的方法名,当你想调用这个方法的时候系统默认调取的是最后导入组件中的那个方法,而不是你想调用的那个方法。这时,你就可以用namespaced:true 设置命名空间解决模块命名冲突问题。
export default {
namespaced: true,//为解决不同模块命名冲突问题
state,
mutations,
actions
}
注意: 与modules不同的是,mutations, actions, getter 没有命名空间的限定,所以要保证全局的唯一性,否则后者会覆盖前者。