官方的解释是:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的全部组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
简单来讲就是集中管理全部的状态
。javascript
对于父子组件以前的通讯,父组件经过porps传递到子组件,子组件经过$emit发送事件都到父组件;css
对于组件与组件之间的通讯,能够new一个新的Vue实例,专门用来作event bus进行通讯。html
当多个组件之间共享一个状态时,event bus可能就变成这样。vue
回到咱们的项目,须要共享状态的总共有3组件:java
这三个组件都须要用到购物车列表goodsListgit
首先安装:cnpm install vuex --save
安装好后,新建一个store文件同时新建index.js文件,引用而且使用vuexes6
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex);
其次,导出一个vuex.Store实例,可接受一个对象做为参数:github
{ state, <!--状态--> getters, <!-- 状态的计算属性 --> mutations, <!-- 用于改变状态 --> actions, <!-- 可包含异步操做的mutation --> }
咱们先只传入stateajax
export const store= new Vuex.Store({ state:{ goodsList:[] } })
接着,在main.js中引入并挂载到Vue实例上vuex
... import {store} from './store/index.js' new Vue({ el: '#app', router, store, render: h => h(App) })
在购物车 car.vue组件经过一个计算属性获取vuex的状态goodsList
computed:{ goodsList(){ return this.$store.state.goodsList } }
这样咱们就能够经过v-for将购物车列表循环出来了,不过如今是数组空的,我把添加的按钮放到电影详情里面去了。
咱们在首页的电影列表套了一层router-link,并将电影的id做为参数,因此点击时就会入到详情页面。 <router-link tag="li" v-for="(v,i) in array" :key="v.id" :to='{path:"/film-detail/"+v.id}'>
在详情页面js中,咱们经过this.$route.params.id获取(参数key为id取自咱们路由的配置)。
获取到id后,接下来就是在某个生命周期(一般是mounted)发送请求获取该电影的data,而后就是赋值操做让数据显示出来了。这里主要讲一下 activated生命周期
,因为咱们在App.vue使用了keep-alive
,因此film-detail组件在第一次进入后就会被缓存
,因为该组件不会被销毁
,因此以后咱们每次进来都会保持第一次进来获取的数据。
所以,咱们将发送请求的时间点由以前的mounted(已挂载)
改变为activated(组件激活时)
,这样既能复用组件,又能保证每次进入时都获取最新的数据。
回到vuex,点击详情里面的按钮时,要将该电影加入到购物车,也就是说要改变state的状态。
vuex规定改变store中的状态的惟一方法是提交mutation
,虽然你也能够直接改变,好比点击某个按钮时 this.$store.state.number++,不过最好时经过mutation触发。一般咱们走路都是正着走的,假如你非要倒立着走,也没办法拦着你。
定义mutation
mutations:{ add:state=>state.number++ }
使用mutation
<!-- 在某个组件里面使用mutation --> this.$store.commit("add");
为了将电影加入购物车,在导出的实例的参数中添加 mutations
export const store= new Vuex.Store({ state:{ goodsList:[] }, mutations:{ addGoods:(state,data)=>{ state.goodsList.push(data); }, } })
点击按钮时,首先判断该电影是否在购物车已存在,若是存在则再也不加入。
var idExist=this.$store.state.goodsList.find((item)=>{ return item.id==id })
使用es6数组新增find函数来实现,该函数返回知足条件的第一个元素。若是不存在该id,则将对应的数据存入。
if(!idExist){ var data={ url:this.smallPic, title:this.title, price:Math.floor(Math.random()*100), stock:"盒", number:1, select:false, id:this.id } this.$store.commit("addGoods",data); this.addSuccess=true; }else{ return alert("已加入購物車") }
为了给加入购物车成功一个反馈,写个简单的效果,让+1缓缓移动而且透明度慢慢消失
<span class="add-to-car__tip show" v-if="addSuccess">+1</span> <!-- 核心css --> span{ animation:move 1.6s forwards; } @keyframes move{ from{ opacity: 1; transform:translateY(0); } to{ opacity: 0; transform:translateY(-100%); } }
当购物车数量大于0时,底部导航须要显示当前购物车的数量,也便是goodsList.length;
能够经过this.$store.state.goodsList.length
来取得,不过最好的办法是经过vuex的getters
来获取。由于假如你有多个页面须要用到这个length时,你可能就会在每一个须要用到的地方复制这段代码过来“this.$store.state.goodsList.length”。
在配置参数中加一个getters
export const store= new Vuex.Store({ state:{ goodsList:[] }, getters:{ goddsNumber:state=>{ return state.goodsList.length } }, mutations:{ addGoods:(state,data)=>{ state.goodsList.push(data); }, } })
使用的方法跟获取state基本一致,只不过是由state.xx改成getters.xx
computed:{ number(){ return this.$store.getters.number } }
咱们但愿当number>0才显示,无非就是一个v-show="number>0"
购物车涉及到的操做有:数量加,数量减,是否选中,删除
,
看起来须要4个方法,实际上总结一下2个方法就够了:
1个是delete
1个是update
哪一个状态
须要改变进行操做便可。好比number+1,或者number-1,或者选中状态为true变为false,由false变为true。咱们只须要将要改变和key
和要设置的value
都做为参数,就能够实现1个方法进行多个操做。在mutation中再加2个方法:
mutations:{ deleteGoods(state,index){ state.goodsList.splice(index,1); }, updateGoods(state,data){ <!--index为操做第几个元素,key为要改变的key,value为新的值 --> const {index,key,value}=data; state.goodsList[index][key]=value; } }
findPosition(id){ return this.goodsList.findIndex(item=>{ return item.id==id }) },
点击删除时:
del(id){ var i=this.findPosition(id); this.$store.commit("deleteGoods",i); },
点击切换选中时:
toggleSelect(id){ var i=this.findPosition(id); var select=this.goodsList[i].select; this.$store.commit("updateGoods",{ index:i, key:"select", value:!select }); }
点击加减号时,传入+1或者-1:
changeNumber(id,val){ var i=this.findPosition(id); var number=this.goodsList[i].number; this.$store.commit("updateGoods",{ index:i, key:"number", value:number+val<=0?1:number+val }) }
vuex提供了mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用,当有多个mutation须要使用时,使用mapMutations可让代码更为简洁。
import { mapMutations } from 'vuex' //在methos中使用展开运算符混入到原有方法中,好比: methods:{ ...mapMutations( ["deleteGoods","updateGoods"](向methods混入2个方法) ), changeNumber(){ ...(原有1个方法) } }
混入后,如今就有3个方法了,能够经过this.deleteGoods和this.updateGoods调用。
...mapMutations({ coolDelete: 'deleteGoods', coolUpdate,'updateGoods' })
这样一来,点击删除时:(更新的也同理)
del(id){ var i=this.findPosition(id); this.coolDelete(i); }
除了mutaion有mapMutation外,state,getters和actions也都有辅助的map函数,可使用Mutation,能够一次获取多个状态和方法。
完整文件以下:store.js
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex); export const store= new Vuex.Store({ state:{ goodsList:localStorage["goodsList"]?JSON.parse(localStorage["goodsList"]): [] }, getters:{ sum:state=>{ var total=0; state.goodsList.forEach((item)=>{ if(item.select){ total+=item.price*item.number } }) return total }, goddsNumber:state=>{ return state.goodsList.length } }, mutations:{ addGoods:(state,data)=>{ state.goodsList.push(data); localStorage.setItem("goodsList",JSON.stringify(state.goodsList)); }, deleteGoods(state,index){ state.goodsList.splice(index,1); localStorage.setItem("goodsList",JSON.stringify(state.goodsList)); }, updateGoods(state,data){ const {index,key,value}=data; state.goodsList[index][key]=value; localStorage.setItem("goodsList",JSON.stringify(state.goodsList)); } } })
car.vue
<template> <div class="car-list-container"> <ul> <li class="car-list" v-for="(v,i) in goodsList"> <div class="car-list__img"> <img :src="v.url"> </div> <div class="car-list__detail"> <p class="car-list__detail__title">{{v.title}}</p> <p class="car-list__detail__number">数量:<button class="number--decrease iconfont icon-jianhao" @click="changeNumber(v.id,-1)"></button><input type="text" readonly="" v-model="v.number"><button class="number--increase iconfont icon-iconfont7" @click="changeNumber(v.id,1)"></button></p> <p class="car-list__detail__type">规格:<span>{{v.stock}}</span></p> <p class="car-list__detail__price">单价:<span>¥{{v.price}}</span></p> <p class="car-list__detail__sum">小计:<span>¥{{v.price*v.number}}</span></p> </div> <div class="car-list__operate"> <span class="iconfont icon-shanchu delete-goods" @click="del(v.id)"></span> <label > <input type="checkbox" name="goods" :checked="v.select==true" @change="toggleSelect(v.id)"> <span></span> </label> </div> </li> </ul> <div class="car-foot-nav"> <button class="sum-price">总额:¥{{sum}}</button> <router-link :to='{name:"index"}' class="continue-shopping" tag="button">继续购物</router-link> <button class="to-pay">去结算</button> </div> </div> </template> <script> import { mapMutations } from 'vuex' import { mapGetters } from 'vuex' export default { name: 'car', data () { return { } }, methods:{ ...mapMutations( ["deleteGoods","updateGoods"] ), findPosition(id){ return this.goodsList.findIndex(item=>{ return item.id==id }) }, changeNumber(id,val){ var i=this.findPosition(id); var number=this.goodsList[i].number; this.updateGoods({ index:i, key:"number", value:number+val<=0?1:number+val }) }, del(id){ var i=this.findPosition(id); this.deleteGoods(i); }, toggleSelect(id){ var i=this.findPosition(id); var select=this.goodsList[i].select; this.updateGoods({ index:i, key:"select", value:!select }) } }, computed:{ ...mapGetters( [ "sum"] ), goodsList(){ return this.$store.state.goodsList } }, mounted(){ this.$ajax.get("/api/car",function(res){ console.log(res) }) } }; </script>
项目地址:https://github.com/linrunzheng/vueApp