在实际开发中经常会遇到多组件访问同一数据的情况,而且需要根据数据的变化做出响应,而这些组件之间并不是父子组件这种简单的关系,在这种情况下,就需要一个全局的状态管理方案。在Vue中,官方推荐使用Vuex。
在项目中应用的场景非常广泛,比如实现购物车,会员登录验证等功能,开发大型项目需要用Vuex来做数据流的管理。
可以使用CDN的方式安装,代码如下:
<!--引用最新版本-->
<script src=”https://unpkg.com/vuex”>
<!--引用指定版本-->
<script src=”https://unpkg.com/[email protected]”>
如果使用模块化开发,则使用NPM方法安装,输入并执行以下命令:
npm install --save vuex
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
在Vue的脚手架项目中使用,在src/main.js文件中的代码如下:
//导入Vuex
import Vuex from "vuex";
//安装插件
Vue.use(Vuex);
//实例化Vuex并调用Store方法
let store=new Vuex.Store({});
new Vue({
router,
store,//注册store
render: h => {return h(App)},
}).$mount('#app')
Vuex使用单一状态树,用一个对象就包含了全部的应用层级状态。作为唯一的数据源而存在,这也意味着,每个应用将仅仅包含一个store实例。
let store=new Vuex.Store({
//状态数据放在state选项中
state:{
count:0
}
});
//1.标签中获取
<template>
<div class="page">
<!--在视图中访问store实例中state选项的count属性值-->
计数器:{{$store.state.count}}
</div>
</template>
//2.js中
<script>
export default {
name: "home",
created(){
//在js中访问store实例中state选项的count属性值
console.log(this.$store.state.count);
}
}
</script>
当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性。
<template>
<div class="page">
<!--在视图中访问store实例中state选项的count属性值-->
计数器:{{count}}
</div>
</template>
import {mapState} from "vuex";
computed:{
//使用扩展运算符提取mapState函数返回的对象属性
...mapState({
//方式1:箭头函数可使代码更简练(推荐使用)
count:state=>state.count,
//方式2:传字符串参数count等同于 `state => state.count`
count:"count",
//方式3:为了能够使用this获取局部状态,可以使用常规函数
count(state){
return state.count
}
方式4:使用数组的方式获取state存放的数据
...mapState(["count"])
})
}
它与计算属性类似相当于computed(只不过这是全局的),有时候我们需要从store中的state中派生出一些状态,例如对列表进行过滤并计数:
// 1.main.js创建的Vuex实例
let store=new Vuex.Store({
state:{
users:[
{id:1,name:"张三",age:18},
{id:2,name:"李四",age:20},
{id:3,name:"王五",age:22},
{id:4,name:"赵六",age:25}
]
},
getters:{
/*
第一个参数:本模块中的state
第二个参数:其他 getter
第三个参数:所有模块的state
*/
getUsers(state,getters,rootState){
//筛选age大于18的数据
let users=state.users.filter((res)=>{
return res.age>18
})
return users;
}
}
});
//2.在需要的组件中使用
<template>
<div class="page">
<ul>
<li v-for="item in $store.getters.getUsers" :key="item.id">
{{item.name}}--{{item.age}}
</li>
</ul>
</div>
</template>
<script>
export default {
name: "home",
created() {
//在js中访问getter
console.log(this.$store.getters.getUsers)
}
}
</script>
<template>
<div class="page">
<ul>
<li v-for="item in getUsers" :key="item.id">
{{item.name}}--{{item.age}}
</li>
</ul>
</div>
</template>
<script>
import {mapGetters} from "vuex";
export default {
name: "home",
computed:{
//使用扩展运算符提取mapGetters函数返回的对象属性
//方式一:数组字符串形式
...mapGetters(["getUsers"]),
//方式二:对象形式(推荐使用)
...mapGetters({
getUsers:"getUsers"
})
}
}
</script>
如果想要改变state中的count的值唯一方法是提交 mutation。Vuex中的 mutation 非常类似于事件:每个mutation都有一个字符串的事件类型(type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受state作为第一个参数。
// 1.main.js 中创建vuex实例
let store=new Vuex.Store({
//状态数据放在state选项中
state:{
count:0
},
mutations:{
//自定义inc方法
inc(state,payload){
state.count+=payload.amount;
}
}
});
//2.在需要的组件中使用
<template>
<div class="page">
<!--在视图中访问store实例中state选项的count属性值-->
计数器:{{$store.state.count}} <button type="button" @click="increment">+</button>
</div>
</template>
<script>
export default {
name: "home",
methods:{
increment(){
//使用commit调用store实例中mutations中的inc方法 载荷提交(推荐使用)
this.$store.commit("inc",{amount:10});
//对象风格的提交
this.$store.commit({
type:"inc",
amount:10
});
}
}
使用常量替代Mutation事件类型(多人开发时可以使用这种形式):
1.首先在src文件夹下创建store文件夹,在该文件夹下创建mutation-types.js文件,该文件的代码如下:
//1.导出一个名为INC的常量值为“inc
export const INC="inc";
2.导入一个名为INC的常量值为“inc”,在src/main.js文件中的代码如下:
//导入INC常量
import {INC} from "./store/mutation-types";
mutations:{
//使用ES6风格的计算属性命名功能来使用一个常量作为函数名
[INC](state,payload){
state.count+=payload.amount;
}
}
在实际开发中,大多数都使用辅助函数的方式提交mutation。
<template>
<div class="page">
<!--在视图中访问store实例中state选项的count属性值-->
计数器:{{count}} <button type="button" @click="increment">+</button>
</div>
</template>
import {mapMutations} from "vuex";
methods:{
//使用ES6扩展运算符提取mapMutations函数返回的对象属性
//方式一(推荐):使用对象的方式,将this.inc()映射为this.$store.commit("inc")
...mapMutations({
inc:"inc"
}),
//方式二:使用字符串数组方式,将this.inc()映射为this.$store.commit("inc")
...mapMutations(["inc"]),
increment(){
//这里的inc方法就是上面...mapMutations(["inc"])中的inc
this.inc({amount:10});
}
},
注意:一条重要的原则就是要记住 mutation必须是同步函数。
如果使用异步函数比如setTimeout,代码执行上不会有任何问题。只是在调试上变得困难,比如我们正在debug一个应用并且观察 devtool 中的 mutation 日志。
devtools是一款基于chrome游览器的插件,用于调试Vue应用,可以去百度搜索vue-devtools进行下载安装,使用非常简单,这里就不在做详细的介绍,因为在开发中没有必要非用vue-devtools调试。
Action类似 mutation,不同在于,Action提交的是 mutation,而不是直接变更状态,Action可以包含任意的异步操作。
//1.实例化Vuex并调用store方法
let store=new Vuex.Store({
//状态数据放在state选项中
state:{
count:0
},
mutations:{
inc(state,payload){
//接收action分发的payload
state.count+=payload.amount;
}
},
actions:{
asyncInc({commit},payload){
//将接收的分发payload,组装新的对象形式的值{amount:payload.amount}使用commit提交给mutation
commit("inc",{amount:payload.amount})
}
}
});
// 2.使用
<template>
<div class="page">
<!--在视图中访问store实例中state选项的count属性值-->
计数器:{{count}} <button type="button" @click="increment">+</button>
</div>
</template>
methods:{
increment(){
//以载荷形式分发
this.$store.dispatch("asyncInc",{amount:10});
//以对象形式分发
this.$store.dispatch({
type:"asyncInc",
amount:10
});
}
},
同样action和mutation一样也有辅助函数使用方式也一样,在实际开发中我们使用辅助函数来进行分发。
<template>
<div class="page">
<div class="page">
<!--在视图中访问store实例中state选项的count属性值-->
计数器:{{count}} <button type="button" @click="increment">+</button>
</div>
</div>
</template>
import {mapActions} from "vuex";
methods:{
//使用扩展运算符ES6扩展运算符提取mapActions函数返回的对象属性
//方式一:使用对象的方式,将this.asyncInc()映射为this.$store.dispatch("asyncInc")
...mapActions({
asyncInc:"asyncInc"
}),
//方式二:使用字符串数组方式,将this.asyncInc()映射为this.$store.dispatch("asyncInc")
...mapActions(["asyncInc"]),
increment(){
//这里的asyncInc方法就是上面...mapActions(["asyncInc"])中的inc
this.asyncInc({amount:10})
}
},
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块,从上至下进行同样方式的分割,例如:
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的,这样使得多个模块能够对同一mutation或action作出响应。 如果希望模块具有更高的封装度和复用性,可以通过添加namespaced: true的方式使其成为带命名空间的模块。当模块被注册后,它的所有getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
let counter={
namespaced:true,//开启支持命名空间
};