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, <!-- 可包含异步操作的mutation -->
}
我们先只传入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作为参数,所以点击时就会入到详情页面。
<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
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){
<!--index为操作第几个元素,key为要改变的key,value为新的值 -->
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
<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
vue.js移动端app实战3:从一个购物车入门vuex的更多相关文章
- vue.js移动端app实战2:首页
貌似有部分人要求写的更详细,这里多写一点vuel-cli基础的配置 什么是vue-cli? 官方的解释是:A simple CLI for scaffolding Vue.js projects, 简 ...
- vue.js移动端app实战2
貌似有部分人要求写的更详细,这里多写一点vuel-cli基础的配置 什么是vue-cli? 官方的解释是:A simple CLI for scaffolding Vue.js projects,简单 ...
- vue.js移动端app实战1:初始配置
本系列将会用vue.js2制作一个移动端的webapp单页面,页面不多,大概在7,8个左右,不过麻雀虽小,五脏俱全,常用的效果如轮播图,下拉刷新,上拉加载,图片懒加载都会用到.css方面也会有一些描述 ...
- vue.js移动端app实战1
本系列将会用vue.js2制作一个移动端的webapp单页面,页面不多,大概在7,8个左右,不过麻雀虽小,五脏俱全,常用的效果如轮播图,下拉刷新,上拉加载,图片懒加载都会用到.css方面也会有一些描述 ...
- vue.js移动端app实战4:上拉加载以及下拉刷新
上拉加载以及下拉刷新都是移动端很常见的功能,在搜索或者一些分类列表页面常常会用到. 跟横向滚动一样,我们还是采用better-scroll这个库来实现.由于better已经更新了新的版本,之前是0.几 ...
- vue.js移动端app:初始配置
本系列将会用vue.js2制作一个移动端的webapp单页面,页面不多,大概在7,8个左右,不过麻雀虽小,五脏俱全,常用的效果如轮播图,下拉刷新,上拉加载,图片懒加载都会用到.css方面也会有一些描述 ...
- ASP.NET Core 与 Vue.js 服务端渲染
http://mgyongyosi.com/2016/Vuejs-server-side-rendering-with-aspnet-core/ 原作者:Mihály Gyöngyösi 译者:oop ...
- NET Core 与 Vue.js 服务端渲染
NET Core 与 Vue.js 服务端渲染 http://mgyongyosi.com/2016/Vuejs-server-side-rendering-with-aspnet-core/原作者: ...
- 基于animate.css动画库的全屏滚动小插件,适用于vue.js(移动端、pc)项目
功能简介 基于animate.css动画库的全屏滚动,适用于vue.js(移动端.pc)项目. 安装 npm install vue-animate-fullpage --save 使用 main.j ...
随机推荐
- Javascript百学不厌-递归
虽然偶尔也用过,但是从来没具体来整理过 普通递归: function fac(n) { ) ; ); } fac() 这是个阶乘.但是占用内存,因为: fac(5) (5*fac(4)) (5*(4* ...
- 分享网上搜到的Oracle中对判定条件where 1=1的正解
今天在网上找到了Oracle中对判定条件where 1=1的正解,粘贴出来和大家分享下 1=1 是永恒成立的,意思无条件的,也就是说在SQL语句里有没有这个1=1都可以. 这个1=1常用于应用程序根据 ...
- Hadoop集群的hbase介绍、搭建、环境、安装
1.hbase的介绍(自行百度hbase,比我总结的全面具体) HBase – Hadoop Database,是一个高可靠性.高性能.面向列.可伸缩的分布式存储系统,利用HBase技术可在廉价PC ...
- 微信小程序怎么用?线下商家最适合玩小程序
随着微信小程序不断地释放新功能,许多行业越来越关注小程序,目前已经有不少餐饮和线下传统零售企业开始谋划利用好小程序.但是,线下商业有着复杂的场景,如何针对自己行业的特点和需求开发出属于自己的小程序,是 ...
- 手机摄像头扫描识别车牌号,移动端车牌识别sdk
一.移动端车牌识别应用背景 (技术交流:18701686857 QQ:283870550) 随着经济水平的不断提高,汽车数量的不断激增为汽车管理带来了不小的难度.路边违章停车的现象越来越频繁.现在, ...
- 导入 theano 失败。“cannot import name gof”
按照网上教程安装好了 theano 之后,import theano 出现错误."cannot import name gof".网上找了很多教程,都不可行.最后找到如下教程.亲测 ...
- (转)log4j(五)——如何控制不同目的地的日志输出?
一:测试环境与log4j(一)——为什么要使用log4j?一样,这里不再重述 1 老规矩,先来个栗子,然后再聊聊感受 package test.log4j.test5; /** * @author l ...
- ES6中的迭代器(Iterator)和生成器(Generator)
前面的话 用循环语句迭代数据时,必须要初始化一个变量来记录每一次迭代在数据集合中的位置,而在许多编程语言中,已经开始通过程序化的方式用迭代器对象返回迭代过程中集合的每一个元素 迭代器的使用可以极大地简 ...
- 网关(Gatesvr) 设计(1)
Gate解决的问题: 1.用户在服务端的实例可以在不同的进程中,也可以移动到同一个进程中.2.用户只需要与服务端建立有限条连接,即可以访问到任意服务进程.这个连接的数量不会随服务进程的数量增长而线性增 ...
- Python初识(PyMysql实例)
为什么学习python呢,好吧,其实我也不知道,反正就是想学习了. 资料什么的全都low了,传值博客免费的就够.不要问我为什么,我基本上都是找的免费的视频.然后传值博客的最多,我真的不是打广告. py ...