js 操作vuex数据_vue.js移动端app实战3:从一个购物车入门vuex

什么是vuex?

官方的解释是:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

简单来说就是集中管理所有的状态。

为什么要用vuex?

对于父子组件之前的通信,父组件通过porps传递到子组件,子组件通过$emit发送事件都到父组件;

对于组件与组件之间的通信,可以new一个新的Vue实例,专门用来做event bus进行通信。

当多个组件之间共享一个状态时,event bus可能就变成这样。

而使用vuex,可以变成这样。

回到我们的项目,需要共享状态的总共有3组件:

这三个组件都需要用到购物车列表goodsList

对于详情页面,需要判断当前点击的电影是否已加入购物车,如果goodsList中已存在,则不再加入;

对于底部导航,当goodsList数量>0时需要显示数字。

购物车组件就不用说了,基本所有的状态都需要。

如何使用

首先安装:cnpm install vuex --save

安装好后,新建一个store文件同时新建index.js文件,引用并且使用vuex

import Vue from 'vue'

import Vuex from 'vuex'

Vue.use(Vuex);

其次,导出一个vuex.Store实例,可接受一个对象作为参数:

{

state,

getters,

mutations,

actions,

}

我们先只传入state

export const store= new Vuex.Store({

state:{

goodsList:[]

}

})

接着,在main.js中引入并挂载到Vue实例上

...

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作为参数,所以点击时就会入到详情页面。

在详情页面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

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缓缓移动并且透明度慢慢消失

+1

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

delete时:获取index后通过splice(index,1);

update时:同样的需要获取index,之后只需要判断哪个状态需要改变进行操作即可。比如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){

const {index,key,value}=data;

state.goodsList[index][key]=value;

}

}

2个方法都需要知道index即需要操作哪一个元素。虽然在购物车这个数组中,我们在循环时已经知道index了,但这不一定就是你需要的那个,除非购物车不分页,一次展示所有数据。假如购物车有100个商品,而且进行了分页,每次取20条数据,那么你点击列表的第一个元素,分页后则会分别对应数组的0,20,40,。。。180,而不分页的第一个元素的index永远是0。因此,我们需要获取元素真正的位置。每个电影都有自己唯一的ID,通过es6数组的findIndex并传入对应的ID,可以返回元素的位置。

写一个方法:

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,可以一次获取多个状态和方法。

至此,基本上已经实现了用vuex进行购物车的增删改。不过每次刷新后,购物车的数据都被清空了。可以配合Localstorage保存到本地。 实现也很简单,每次mutation操作后将state中的goodsList存入到localstorage即可。每次启动服务后,判断localstorage是否有值,有值得话用json.parse转化为数组赋值给state.goodList,没有值得话则为state.goodsList设置默认值为空数组[ ];

完整文件如下: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

  • {{v.title}}

    数量:

    规格:{{v.stock}}

    单价:¥{{v.price}}

    小计:¥{{v.price*v.number}}

总额:¥{{sum}}

继续购物

去结算

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)

})

}

};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值