vue.js移动端app实战3:从一个购物车入门vuex

什么是vuex?

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

简单来讲就是集中管理全部的状态javascript

为何要用vuex?

  • 对于父子组件以前的通讯,父组件经过porps传递到子组件,子组件经过$emit发送事件都到父组件;css

  • 对于组件与组件之间的通讯,能够new一个新的Vue实例,专门用来作event bus进行通讯。html

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

而使用vuex,能够变成这样。

回到咱们的项目,须要共享状态的总共有3组件:java

这三个组件都须要用到购物车列表goodsListgit

  • 对于详情页面,须要判断当前点击的电影是否已加入购物车,若是goodsList中已存在,则再也不加入;
  • 对于底部导航,当goodsList数量>0时须要显示数字。
  • 购物车组件就不用说了,基本全部的状态都须要。

如何使用

首先安装: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

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>

代码已上传到github,点击查看

项目地址:https://github.com/linrunzheng/vueApp

相关文章
相关标签/搜索