文章目录
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
Vuex是在组件外部管理状态,是用来管理组件之间通信的一个插件。这里的状态就是指数据。
这一次我们用Vuex来模拟一个类似购物车的例子,这里并没有实现真正的购物车,目的是用最简单的代码演示Vuex的知识点。
安装vuex
和安装vue.js一样,直接用
<script src="./vue.js"></script>
<script src="./vuex.js"></script>
引入后,相当于注册了全局属性Vuex
创建store
Vue中每个应用将仅仅包含一个 store实例,store可以理解为仓库。
下面是本例中代码的结构,首先创建一个store实例,参数是个option对象,其中包含属性state、getters、mutations、actions。
<div id="app">
</div>
<script src="./vue.js"></script>
<script src="./vuex.js"></script>
<script>
const myStore = new Vuex.Store({
state: {},
getters:{},
mutations:{},
actions:{},
});
const {mapState,mapGetters,mapActions,mapMutations} =Vuex;
new Vue({
});
</script>
Store方法的参数:
state
在state中存放状态(数据),它的值是一个对象,是供我们全局使用的状态。
本例中,定义一个数组代表购物车列表
state: {
shoppingCart: [{id: 101, name: '苹果', count: 1, price: 2}]
},
getters
从 state 中可以派生出一些状态,如本例中,购物车里的数据可以派生出总金额,这个数据依赖于state,派生状态保存在getters中, getters类似Vue的computed属性,是store的计算属性,与computed属性一样,getter的返回值会根据它的依赖被缓存起来,只有当它的依赖值发生了改变才会被重新计算。
参数:Getter 接受 state 作为其第一个参数。
getters : {
//购物车里商品总金额
totalMoney:(state)=>state.shoppingCart.reduce((prev, current)=> prev + current.count * current.price, 0)
},
mutations
在mutations选项中定义mutation方法,用于修改state,规定更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
参数:
mutation方法中接受两个参数,第一个是state,第二个是用户传入的参数,这里称为payload,格外的参数,可选。
调用方式:
mutation方法通过 store.commit('mutation方法名',值)
方法触发,实现同步操作store中的数据。
本例中,只有两个mutation方法,add方法用于给购物车增加一条记录,remove方法用于删除一条记录。
mutations: {
//add方法判断购物车中是否有添加的商品,如果有增加数量,如果没有添加一条记录
add: (state, payload) => {
let isHas = state.shoppingCart.some((item) => {
if (item.id == payload.id) {
item.count += payload.count;
return true;
}
})
if (!isHas) state.shoppingCart.push(payload);
},
//购物车中移除一条记录,这里用es6解构赋值写法
remove: ({shoppingCart}, id) => {
let i = shoppingCart.findIndex((item) => item.id === id);
shoppingCart.splice(i, 1);
}
},
actions
从store的外部看action方法和mutation方法都用于修改state,但在store内部,实际上action不能直接操作state,在其内部需要去触发mutation方法来实现修改State。
- action方法用于定义异步操作逻辑,以满足某些业务场景要求。
- action方法通过 store.dispatch(‘action方法名’,值) 方法触发。
- action方法接受
context
对象作为第一个参数,payload 作为第二个参数(可选)
context 对象
与store 实例具有相同方法和属性。
本例中,假设业务逻辑是添加购物车时需要到后端验证库存余额,这里用setTimeout模拟一个异步请求,异步成功后触发mutation方法add,在mutation方法中修改state,这是store的内部逻辑,在store之外看调用action方法即修改了state。
actions: {
add:(context, payload)=>{
setTimeout(()=>{
let code = 0;
if (code === 0) {
context.commit('add', payload); //触发mutation方法
}
},2000 * Math.random())
}
},
现在完成了这个例子中store的创建,在控制台中打印一下这个对象查看一下。
Vue部分
模板
本例中使用了2个组件,根组件和购物车组件
<div id="app">
<div class="productList">
<h2>商品目录</h2>
<table border="1">
<tr>
<th>编号</th>
<th>商品名称</th>
<th>单价</th>
<th>数量</th>
<th>操作</th>
</tr>
<tr v-for="(item,i) in productList" :key="item.id">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.price }}</td>
<td>{{item.count}}</td>
<td>
<button v-on:click="add({id:item.id,name:item.name,price:item.price,count:1})">添加到购物车</button>
</td>
</tr>
</table>
</div>
<shopping-cart inline-template>
<div class="shoppingCart" v-if="shoppingCart.length>0">
<h2>购物车</h2>
<table border="1">
<tr>
<th>编号</th>
<th>商品名称</th>
<th>单价</th>
<th>数量</th>
<th>操作</th>
</tr>
<tr v-for="item in shoppingCart" :key="item.id" >
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.price }}</td>
<td>{{item.count}}</td>
<td>
<button v-on:click="remove(item.id)">删除</button>
</td>
</tr>
</table>
<p>总金额:{{totalMoney}}元</p>
</div>
</shopping-cart>
</div>
Vue实例
在Vue中获取和操作Store之前,首先要引入Store,在vue实例的store选项中引入Store实例,这样做使得整个实例(包含子组件)都可以通过this.$store获取到定义的Store实例。
new Vue({
el: "#app",
store:myStore, // 把创建的myStore对象提供给vue实例的store选项
data: {
productList: [
{id: 101, name: '苹果', count: 5, price: 2},
{id: 102, name: '香蕉', count: 10, price: 3},
{id: 103, name: '菠萝', count: 15, price: 4}
]
},
components: {
'shopping-cart': {
computed: {
shoppingCart(){
return this.$store.state.shoppingCart;
},
totalMoney() {
return this.$store.getters.totalMoney;
}
},
methods: {
remove(id) {
this.$store.commit('remove', id);
}
}
}
},
methods: {
add(product) {
this.$store.dispatch('add', product);
}
},
});
Vuex中的辅助函数
在前面的代码中有这样一句:
const {mapState,mapGetters,mapActions,mapMutations} =Vuex;
定义了这些常量,但并没有使用,实际上他们是Vuex中提供了4个辅助函数,用于将vuex.store中的属性映射到vue实例上,为在vue实例中获取和操作vuex.store提供便利。
下面将使用辅助函数替换掉原来的代码
mapState辅助函数
mapState是state的辅助函数,用于把state属性映射到computed上。
- mapState函数接受一个对象或数组作为参数;
- 返回一个数组对象,数组成员形式为
键名:函数
。
用一个例子理解一下mapState函数,其他几个辅助函数与之相似
const {mapState,mapGetters,mapActions,mapMutations} =Vuex;
let obj={a:1,b:2};
console.log(obj,'->',Vuex.mapState(obj));
console.log(Vuex.mapState(obj),'->',{...Vuex.mapState(obj), c:3});
console.log('------------------------------------');
let arr=['a','b'];
console.log(arr,'->',Vuex.mapState(arr));
console.log(Vuex.mapState(arr),'->',{...Vuex.mapState(arr), c:3});
用对象展开运算符(…)可以将对象混入到外部对象中。
懂了mapState辅助函数的作用,我们来看一下其他几个辅助函数
const {mapState,mapGetters,mapActions,mapMutations} =Vuex;
console.log(myStore.state,'->',mapState(myStore.state));
console.log(myStore.getters,'->',mapGetters(myStore.getters));
console.log(myStore._actions,'->',mapActions(myStore._actions));
console.log(myStore._mutations,'->',mapMutations(myStore._mutations));
替换shopping-cart组件中的computed中的代码。
'shopping-cart': {
computed: {
...mapState(['shoppingCart']), //当映射的计算属性的名称与 state的子节点名称相同时,可以给 mapState 传一个字符串数组。
totalMoney() {
return this.$store.getters.totalMoney;
}
},
}
也可以这样写
...mapState({
shoppingCart:'shoppingCart' // 传字符串参数 'shoppingCart' 等同于 `state => state.shoppingCart`
}),
PS:mapState可以接受数组或者对象作为参数,在大型项目中给mapState传递一个对象会更合适,shoppingCart实际上是一个别名,这样可以解决命名冲突的问题,也可以代替state里过长的属性名。
mapGetters辅助函数
把getters属性映射到computed上,mapGetters函数和mapState相似
mapActions辅助函数
把actions里面的方法映射到methods中
mapMutations辅助函数
把mutations里面的方法映射到methods中
使用前面的4个辅助函数对代码进行改写
new Vue({
el: "#app",
store,
data: {
productList: [
{id: 101, name: '苹果', count: 5, price: 2},
{id: 102, name: '香蕉', count: 10, price: 3},
{id: 103, name: '菠萝', count: 15, price: 4}
]
},
components: {
'shopping-cart': {
computed: {
...mapState(['shoppingCart']), // 把this.shoppingCart 映射为 this.$store.state.shoppingCart
...mapGetters(['totalMoney']) //把this.totalMoney映射为this.$store.getters.totalMoney
},
methods: {
...mapMutations(['remove']) //把this.remove()映射为 this.$store.commit('remove')
}
}
},
methods: {
...mapActions(['add']) //将this.add()映射为this.$store.dispatch('add')
},
});
Vuex存储和本地存储
- Vuex存储的状态(数据)以
对象
的形式保存在内存中,当刷新页面(清除内存)时vuex存储的值会丢失; - localstorage(本地存储)以文件的方式存储在本地,永久保存;
- sessionstorage( 会话存储 )以文件的方式存储在本地,浏览器关闭即被清除。
localStorage和sessionStorage只能存储字符串类型,可以使用ECMAScript提供的JSON对象的stringify和parse来处理字符串。
Vuex用于组件之间的传值,localstorage,sessionstorage则主要用于不同页面之间的传值,在单页Vue应用中,似乎它们之间可以互相代替。实际在真实业务中,它们是结合使用的,浏览器无法记住Vuex存储的数据(数据保存在内存)一般会采用localStorage、sessionStorage或者cookie等来保存需要浏览器记住的数据。