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

相關文章
相關標籤/搜索