官方的解釋是: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