利用vue重構(gòu)有贊商城的思路以及總結(jié)整理
這個(gè)是我的第一個(gè)vue項(xiàng)目,歷時(shí)了兩個(gè)多星期完成吧,通過(guò)這個(gè)項(xiàng)目了解了vue框架的基本語(yǔ)法以及生命周期等特性,并且了解了vue-loader、vue-cli、vue-router、vuex的基本使用方法,以及使用到axios,swiper,mint-ui,Volecity庫(kù),收獲頗深,因此想在此簡(jiǎn)單地?cái)⑹鲆幌轮貥?gòu)有贊商城的基本思路以及當(dāng)中的一些重要操作,另外當(dāng)作對(duì)自己項(xiàng)目的回顧以及相關(guān)vue知識(shí)的總結(jié)。
以下是本次項(xiàng)目的代碼鏈接和預(yù)覽鏈接:
代碼鏈接:https://github.com/Leonardo-zyh/Vue-youzanStore
預(yù)覽鏈接:https://leonardo-zyh.github.io/Vue-youzanStore/dist1/
首先這次重構(gòu)有贊商城使用的是一個(gè)多頁(yè)面應(yīng)用的重構(gòu)思路,因此在進(jìn)行重構(gòu)之前要對(duì)項(xiàng)目文件進(jìn)行一些配置和調(diào)整,具體的操作的話可以點(diǎn)擊以下這個(gè)鏈接進(jìn)行查看:基于vue-cli搭建一個(gè)多頁(yè)面應(yīng)用
在完成了多頁(yè)面應(yīng)用的基礎(chǔ)結(jié)構(gòu)的搭建之后,會(huì)出現(xiàn)項(xiàng)目根目錄下有一個(gè)src文件夾,src文件里有components、modules、pages三個(gè)文件夾的情況,而components文件夾是用來(lái)放置一些共用的vue組件的,而modules文件夾里是放置一些共用的css、js模塊,至于最后的pages文件夾則是用來(lái)放置有贊商城的不同頁(yè)面的文件,每個(gè)頁(yè)面都會(huì)在pages內(nèi)呈一個(gè)單獨(dú)的文件夾,里面會(huì)放置關(guān)于這個(gè)頁(yè)面的獨(dú)有的所有文件。
在這里先說(shuō)明一下,重構(gòu)過(guò)程中所有獲取到的數(shù)據(jù),都是通過(guò)使用在easymock上編寫對(duì)應(yīng)的接口(原在數(shù)據(jù)在rap2上,但是接口數(shù)據(jù)不穩(wěn)定且無(wú)法搭建在github上),然后通過(guò)axios發(fā)送異步請(qǐng)求來(lái)獲取到的模擬的數(shù)據(jù),這是模仿真實(shí)的開發(fā)環(huán)境下的操作,具體的實(shí)現(xiàn)過(guò)程的話可以參考easymock以及我在github上面的源碼文件。
以下是重構(gòu)有贊商城的所需的頁(yè)面列表,一共有六個(gè)頁(yè)面,分別為:
1.首頁(yè)
2.目錄分類頁(yè)
3.商品搜索列表頁(yè)
4.商品詳情頁(yè)
5.購(gòu)物車頁(yè)面
6.個(gè)人中心地址管理頁(yè)面
接下來(lái)我們會(huì)逐個(gè)頁(yè)面來(lái)說(shuō)說(shuō)他們的重構(gòu)思路
然后我們會(huì)用到的一些第三方插件,分別為:
- axios
- swiper
- mint-ui
- Volecity
- qs庫(kù)
一、首頁(yè)
首頁(yè)的整體結(jié)構(gòu)是,頂部一個(gè)無(wú)縫輪播組件,中間是三個(gè)推薦的商店鏈接,然后再下面是一個(gè)“最熱商品推薦”的商品列表,然后最底下是一個(gè)底部導(dǎo)航欄組件一共四部分,對(duì)于中間三個(gè)推薦商店鏈接我這邊暫不做處理,原因是關(guān)于并沒(méi)有重構(gòu)商店頁(yè)面的計(jì)劃。
1、無(wú)縫輪播組件
那我們首先來(lái)說(shuō)一下輪播組件,首先我們需要在src目錄下的compnents文件夾里新建一個(gè)輪播組件文件,輪播的話我們會(huì)直接選擇使用swiper插件提供的輪播組件庫(kù),我們只需把它封裝到一個(gè)組件文件中即可,具體的操作在這里我就不詳細(xì)說(shuō)明了,這里只強(qiáng)調(diào)兩個(gè)需要注意的問(wèn)題:
1.應(yīng)不應(yīng)該在輪播組件放入圖片數(shù)據(jù)呢?
回答:不應(yīng)該,原因是為了使得輪播組件獨(dú)立出來(lái),在不同的組件中得以復(fù)用,并且使其可以適應(yīng)不同規(guī)格不同數(shù)量的圖片,因此我們的輪播組件只負(fù)責(zé)展示數(shù)據(jù),不負(fù)責(zé)拿數(shù)據(jù),數(shù)據(jù)應(yīng)該通過(guò)props從父組件中獲取。
<Swipe :lists="bannerLists" name="swpie.vue" v-if="bannerLists"></Swipe>
new Swiper('.swiper-container',{ loop:true, pagination: '.swiper-pagination', autoplay: 2000 }) getBanner(){//獲取輪播數(shù)據(jù) this.$http.get(url.banner).then(res=>{ this.bannerLists = res.data.lists })
2.關(guān)于swiper的配置應(yīng)將其寫在輪播組件的生命周期的哪一部分呢?
回答:首先我們需要了解的是swiper是對(duì)DOM節(jié)點(diǎn)進(jìn)行操作的,所以swiper的配置應(yīng)該寫在組件的mounted生命周期鉤子里,因?yàn)樵谶@個(gè)階段已經(jīng)在頁(yè)面上生成了該組件對(duì)應(yīng)的DOM節(jié)點(diǎn);另一方面,swiper組件里的數(shù)據(jù)是swiper的父組件異步獲取后傳遞給swiper的,因此應(yīng)該等swiper拿到了傳遞的數(shù)據(jù)之后再對(duì)這個(gè)組件進(jìn)行渲染,因此需要給這個(gè)組件添加一個(gè)v-if="bannerLists"
的判斷,判斷swiper組件是否獲取到數(shù)據(jù),只有獲取到了數(shù)據(jù)才生成這個(gè)DOM節(jié)點(diǎn)。
2、“最熱商品推薦”的商品列表
關(guān)于這個(gè)“最熱商品推薦”的商品列表的重構(gòu)也非常簡(jiǎn)單,只需通過(guò)axios發(fā)送你想獲取的商品列表的頁(yè)數(shù)和每頁(yè)的展示商品的個(gè)數(shù)的請(qǐng)求到對(duì)應(yīng)的接口中,就可以獲取到對(duì)應(yīng)的商品列表的數(shù)據(jù),然后再通過(guò)v-for
把每個(gè)商品的圖片、名稱和價(jià)格渲染到頁(yè)面中即可。
同樣的,這里有兩個(gè)值得注意的問(wèn)題:
1.獲取到的價(jià)格的格式并不統(tǒng)一,如何來(lái)使得這些價(jià)格的格式統(tǒng)一起來(lái)?
回答:這里需要用到vue實(shí)例里的一個(gè)自帶屬性filters
來(lái)對(duì)數(shù)據(jù)進(jìn)行過(guò)濾,在vue1.0的時(shí)候,filters里面會(huì)有自帶的過(guò)濾器,不過(guò)在vue2.0時(shí)被移除了,因此需要我們來(lái)自己寫所需的過(guò)濾器的過(guò)濾方式:
filters:{ currency(num){ num=num+'' let arr=num.split('.') if (arr.length===1){ return num+'.00' } else { if (arr[1].length===1){ return num+'0' } else return num } } }
只有在渲染頁(yè)面時(shí),只要對(duì)你想進(jìn)行的數(shù)據(jù)后加上該過(guò)濾器即可:
<div class="price">¥{{list.price | currency}}</div>
2.如何做到下來(lái)商品列表就發(fā)送對(duì)應(yīng)的請(qǐng)求來(lái)更新一頁(yè)新的商品列表?
回答:這里我們使用到了mint-ui,一個(gè)移動(dòng)端分頁(yè)效果庫(kù),然后我們使用它文檔上面對(duì)應(yīng)的infinite scroll的api來(lái)達(dá)到我們想要的效果,具體代碼如下:
<ul class="js-list js-lazy" data-src="" v-infinite-scroll="getList" infinite-scroll-disabled="loading" infinite-scroll-distance="50" > <li v-for="list in lists" :key="list.id"> <div class="goods-item"> <a :href="'/goods.html?id='+list.id" rel="external nofollow" > <div class="thumb img-box"> <img class="fadeIn" v-bind:src="list.img"> </div> <div class="detail"> <div class="title">{{list.name}}</div> <div class="price">¥{{list.price | currency}}</div> </div> </a> </div> </li> </ul>
上述代碼中,v-infinite-scroll="getList"
表示每當(dāng)下拉到一定距離時(shí)就觸發(fā)methods里面的getList方法;getList方法的具體代碼如下所示:
getList(){ if (this.allLoad) return this.loading=true axios.post(url.hostLists,{ pageNum:this.pageNum, pageSize:this.pageSize }).then((response)=>{ let currentList=response.data.lists if (currentList.length<this.pageSize) this.allLoad=true if (this.lists) { this.lists=this.lists.concat(currentList) } else { this.lists=currentList } this.pageNum +=1 this.loading=false }) }
infinite-scroll-disabled="loading"
表示效果觸發(fā)的條件,若loading為false則表示可以觸發(fā),若loading為true則表示不能觸發(fā),因此當(dāng)loading為true時(shí)我們可以給底部添加一個(gè)加載效果,當(dāng)數(shù)據(jù)獲取完畢,loading變?yōu)閒alse時(shí),我們可以通過(guò)v-show="loading"
來(lái)讓加載效果消失;infinite-scroll-distance="50"
表示下拉的觸發(fā)距離,設(shè)置的數(shù)值越大,表示滾動(dòng)條離底部的觸發(fā)距離越大,越容易觸發(fā)。
3.底部導(dǎo)航欄組件
底部導(dǎo)航欄和輪播組件一樣,由于可以在其他地方進(jìn)行復(fù)用,因此會(huì)把該組件放于components文件夾中,這里值得一提的是,底部導(dǎo)航欄組件由于點(diǎn)擊不同的圖標(biāo),它會(huì)跳轉(zhuǎn)到不同的頁(yè)面,因此會(huì)導(dǎo)致導(dǎo)航欄狀態(tài)的重新加載,因此,若想要在不同的頁(yè)面讓導(dǎo)航欄呈現(xiàn)不同的狀態(tài),我們需要在跳轉(zhuǎn)的時(shí)候傳入對(duì)應(yīng)的查詢參數(shù),然后在跳轉(zhuǎn)到不同的頁(yè)面時(shí)讀取這個(gè)參數(shù)來(lái)呈現(xiàn)對(duì)應(yīng)的不同的狀態(tài),具體的代碼片段如下:
let {index}=qs.parse(window.location.search.substring(1)) export default { data(){ return { navConfig, curIndex:parseInt(index,10) || 0 } }, methods:{ changeNav(index,list){ location.href=`${list.href}?index=${index}` } } }
值得一提的是,在這里我們使用到了一個(gè)qs庫(kù),這個(gè)庫(kù)可以方便我們提取出當(dāng)前url后面的查詢參數(shù)。
最后,由于在其他頁(yè)面中,filters屬性和底部導(dǎo)航欄組件都可以進(jìn)行復(fù)用,所以這里我們利用mixins屬性,來(lái)對(duì)filters屬性和底部導(dǎo)航欄組件的注入進(jìn)行打包,打包在一個(gè)js文件夾下的mixin.js文件中:
import Footnav from 'components/FootNav.vue' let mixin={ filters:{ currency(num){ num=num+'' let arr=num.split('.') if (arr.length===1){ return num+'.00' } else { if (arr[1].length===1){ return num+'0' } else return num } } }, components:{ Footnav } } export default mixin
當(dāng)你的頁(yè)面需要使用到該過(guò)濾器,或者底部導(dǎo)航欄時(shí),只要對(duì)這個(gè)模塊進(jìn)行引入,并在mixins屬性中添加它即可:
new Vue({ ... mixins:[mixin] })
二、目錄分類頁(yè)
目錄分類頁(yè)并無(wú)新的操作,和首頁(yè)的部分操作類似,就是利用axios從接口中獲取數(shù)據(jù)并渲染到頁(yè)面中,并對(duì)頁(yè)面中的一些焦點(diǎn)狀態(tài)進(jìn)行v-show的處理,以及一些類名和焦點(diǎn)的處理,我們可以從目錄分類頁(yè)中通過(guò)點(diǎn)擊熱銷商品進(jìn)入商品詳情頁(yè),通過(guò)點(diǎn)擊熱門品牌進(jìn)入商品搜索列表頁(yè),在進(jìn)行這些頁(yè)面的跳轉(zhuǎn)時(shí),把一些關(guān)鍵的數(shù)據(jù)傳入查詢參數(shù)中以便跳轉(zhuǎn)頁(yè)面獲取即可。
let { index } = qs.parse(location.search.substr(1)); changeNav(list, index) { //this.curIndex = index; location.href = `${list.href}?index=${index}`; //頁(yè)面跳轉(zhuǎn) event.preventDefault(); }
三、商品搜索列表頁(yè)
商品搜索列表頁(yè)同樣的基本操作與首頁(yè)和目錄分類頁(yè)類似,這里唯一不同的是,商品搜索列表頁(yè)在用戶下拉一定距離后,會(huì)出現(xiàn)一個(gè)回到頂部的圖標(biāo),點(diǎn)擊圖標(biāo),用戶就可以直接回到頂部,在這里,我們使用了一個(gè)叫作Velocity.js的動(dòng)畫庫(kù),它是把css中的一些動(dòng)畫效果進(jìn)行封裝,進(jìn)而可以通過(guò)一些api輕松實(shí)現(xiàn)一些簡(jiǎn)單的動(dòng)畫效果,例如上面所說(shuō)的回到頂部,在項(xiàng)目中的代碼片段如下所示:
//引入Velocity import Velocity from 'velocity-animate/velocity.js' //在methods中加入對(duì)應(yīng)方法 methods:{ scrollMove(){ if (window.scrollY>=290){ this.isShow=true } else { this.isShow=false } }, goToTop(){ Velocity(document.body, 'scroll', { duration: 500, easing: "easeOutQuart" }); this.isShow=false //回到頂部圖標(biāo)消失 } }
四、商品詳情頁(yè)
在商品詳情頁(yè)中,除了有對(duì)數(shù)據(jù)的獲取和頁(yè)面的渲染外,這里主要涉及到了三個(gè)新的操作:
- sku算法的應(yīng)用
- 頁(yè)面的載入和消失的動(dòng)畫效果
- 頁(yè)面展示時(shí)的穿透滾動(dòng)問(wèn)題的解決
首先是sku算法,由于此次商品詳情頁(yè)的選擇并不需要使用到它,因?yàn)樯唐返目蛇x屬性只有一個(gè),但是在實(shí)際情況下,由于很多商品的可選屬性不止一個(gè),因此是需要使用到sku算法的。SKU=Stock Keeping Unit(庫(kù)存量單位),同一型號(hào)的產(chǎn)品,或者說(shuō)是同一個(gè)產(chǎn)品項(xiàng)目(產(chǎn)品條形碼是針對(duì)企業(yè)的產(chǎn)品)。
然后如何制作sku頁(yè)面載入和消失時(shí)的動(dòng)畫效果呢?這里我們使用到了vue提供的自帶transition的封裝組件,可以通過(guò)這個(gè)組件來(lái)給任何元素和組件添加進(jìn)入或者離開時(shí)的過(guò)渡。這個(gè)組件提供了八個(gè)JavaScript鉤子函數(shù)以及六個(gè)過(guò)渡類名的切換,利用這些鉤子函數(shù)以及類名的切換就可以完成組件的過(guò)渡動(dòng)畫了,這里列舉一個(gè)vue文檔上的典型例子給大家參考一下吧:
transition過(guò)渡:
<div id="demo"> <button v-on:click="show = !show"> Toggle </button> <transition name="fade"> <p v-if="show">hello</p> </transition> </div>
new Vue({ el: '#demo', data: { show: true } })
.fade-enter-active, .fade-leave-active { transition: opacity .5s; } .fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ { opacity: 0; }
最后在遮罩層和彈出層出現(xiàn)之后(即sku頁(yè)面),對(duì)頁(yè)面進(jìn)行上下拖動(dòng)時(shí),背后的內(nèi)容層也會(huì)跟著一起拖動(dòng),這是典型的滾動(dòng)穿透問(wèn)題,在這里我的解決辦法是監(jiān)聽遮罩層和彈出層的出現(xiàn),當(dāng)它們出現(xiàn)之后,我們更改內(nèi)容層的樣式,使其樣式變?yōu)椋?code>position:fixed;width:100%;這樣內(nèi)容層就不會(huì)再滾動(dòng)了,之后我們?cè)偻ㄟ^(guò)設(shè)置:
scrollTop = document.scrollingElement.scrollTop document.body.style.top = -scrollTop + 'px'
使得內(nèi)容層的滾動(dòng)高度與遮罩層和彈出層出現(xiàn)前相同,并且,調(diào)整html元素的樣式:height:100%;overflow:hidden;
,在關(guān)閉遮罩層和彈出層后,還原這些修改樣式,即可使得滾動(dòng)穿透的問(wèn)題得以解決。需要注意的是,還原這些樣式之后,原本內(nèi)容層滾動(dòng)的高度就會(huì)丟失,因此我們要通過(guò)之前記錄下來(lái)內(nèi)容層滾動(dòng)的高度,在還原樣式時(shí)將滾動(dòng)高度也一并還原。
document.scrollingElement.scrollTop = scrollTop
這樣滾動(dòng)穿透的問(wèn)題就算是徹底解決了,下面是全部的這部分的全部代碼片段:
chooseSku(type) {//顯示購(gòu)買菜單 this.skuType = type this.showModal = true }, changeSku(num) {//增減數(shù)量 if (num < 0 && this.skuNum === 1) return this.skuNum += num }, addCart() {//加入購(gòu)物車 $.ajax($.url.cartAdd, { id, skuNum: this.skuNum }).then((data) => { if (data.status === 200) { this.showModal = false this.showAddMsg = true //添加成功的信息 this.isAddedCart = true //顯示購(gòu)物車圖標(biāo) setTimeout(() => this.showAddMsg = false, 1200) } }) } },
watch:{ showSku(val,oldVal){ if (val){ scrollTop = document.scrollingElement.scrollTop document.body.style.top = -scrollTop + 'px' } document.body.style.position=val?'fixed':'static' // document.body.style.margin=val?`0 0 ${window.scrollY}px 0`:'0px' document.querySelector('html').style.overflow=val?'hidden':'auto' document.body.style.width=val?'100%':'auto' document.querySelector('html').style.height=val?'100%':'auto' if (!val){ document.scrollingElement.scrollTop = scrollTop } } }
WeUI上面的層級(jí)規(guī)范:用于規(guī)范WeUI頁(yè)面元素所屬層級(jí)、層級(jí)順序及組合規(guī)范。
五、購(gòu)物車頁(yè)面
購(gòu)物車頁(yè)面里面比較多的邏輯關(guān)系,在此就不一一枚舉了,大概說(shuō)一下它的重構(gòu)思路:
商品的獲取渲染以及增加是否被選中屬性
獲取后臺(tái)數(shù)據(jù)加載處理或動(dòng)態(tài)響應(yīng)式處理
商品選中店鋪選中全選,影響價(jià)格三級(jí)聯(lián)動(dòng)。
編輯狀態(tài),其余不可切換。對(duì)數(shù)量操作,加減更改。刪除,單商品刪除,選中(多個(gè))刪除,商品刪除店鋪刪除。
原生事件,滑動(dòng)刪除頁(yè)面,Volecity。
刪除多個(gè)商品進(jìn)行過(guò)濾處理
fetch層封裝,
同一個(gè)場(chǎng)景下思維層封裝
問(wèn)題呈現(xiàn),左滑刪除樣式繼承。[0].style.left='0px' this.$refs[`goods-${shopIndex}-${goodIndex}`][0].style.left='0px'
ref 是非響應(yīng)式的,不建議在模板中進(jìn)行數(shù)據(jù)綁定,即使用唯一標(biāo)識(shí)綁定
v-for 模式使用“就地復(fù)用”策略,簡(jiǎn)單理解就是會(huì)復(fù)用原有的dom結(jié)構(gòu),盡量減少dom重排來(lái)提高性能 ( 解決方案:還原dom樣式 )
key 為每個(gè)節(jié)點(diǎn)提供身份標(biāo)識(shí),數(shù)據(jù)改變時(shí)會(huì)重排,最好綁定唯一標(biāo)識(shí),如果用index標(biāo)識(shí)可能得不到想要的效果(綁定唯一識(shí)別key)
網(wǎng)頁(yè)性能管理詳解
首先獲取數(shù)據(jù),渲染到頁(yè)面這些是基本的操作
獲取到數(shù)據(jù)之后,由于有一些屬性數(shù)據(jù)中沒(méi)有,并且我們想要它在頁(yè)面中是呈響應(yīng)式存在的,因此從接口獲取到數(shù)據(jù)之后不應(yīng)該直接賦值給data里,而是應(yīng)該先給數(shù)據(jù)增添屬性,再把增添后的數(shù)據(jù)賦值到data處,具體代碼如下:
getLists(){ cart.getCartLists().then((response)=>{ let list=response.data.cartList list.forEach(shop=>{ shop.checked=true shop.editingStatus=false shop.editingMsg='編輯' shop.removeChecked=false shop.goodsList.forEach(good=>{ good.checked=true good.removeChecked=false good.touchDelete=false }) }) this.cartLists=list }) }
每次選擇了商店下的商品時(shí),都利用數(shù)組的every方法來(lái)遍歷數(shù)組看是否全部商品都被選中了,若選中則商店也被選中,同理,若選擇了商店,則遍歷商店下的商品,把商店下的商品全部選中。取消選中亦是同理。
全選與否則利用計(jì)算屬性來(lái)處理,利用計(jì)算屬性的getter來(lái)獲取此時(shí)購(gòu)物車的狀態(tài)來(lái)判斷是否被全選,利用計(jì)算屬性的setter來(lái)處理點(diǎn)擊全選時(shí)商店及商店下商品的狀態(tài)的改變。
同樣的,利用計(jì)算屬性來(lái)計(jì)算正常狀態(tài)下選中商品的總價(jià),并返回選中商品的列表。同理,利用計(jì)算屬性來(lái)計(jì)算編輯狀態(tài)下的選中的商品的列表,并以數(shù)組的形式返回。
在編輯狀態(tài)下,我利用了計(jì)算屬性來(lái)對(duì)商品的數(shù)量的數(shù)據(jù)進(jìn)行了監(jiān)測(cè),若判斷出數(shù)量中存在非數(shù)字或者負(fù)數(shù)的情況,則會(huì)自動(dòng)把數(shù)量的數(shù)據(jù)變成1。
利用touchstart
和touchend
兩個(gè)事件來(lái)實(shí)現(xiàn)商品左拉刪除的功能,這兩個(gè)事件分別綁定start
和end
的方法,方法的具體代碼如下所示:
start(e,good){ good.startX=e.changedTouches[0].clientX }, end(e,good,goodIndex,shopIndex,shop){ let endX=e.changedTouches[0].clientX let left='0px' if (good.startX-endX>100){ good.touchDelete=true left='-60px' Velocity(this.$refs[`goods-${shopIndex}-${goodIndex}`],{left}) shop.goodsList.forEach((otherGood,index)=>{ if (otherGood.touchDelete && index!==goodIndex) { otherGood.touchDelete=false Velocity(this.$refs[`goods-${shopIndex}-${index}`],{left:'0px'}) } }) } else if (endX-good.startX>100) { good.touchDelete=false left='0px' Velocity(this.$refs[`goods-${shopIndex}-${goodIndex}`],{left}) } }
當(dāng)添加了左拉刪除的功能之后,頁(yè)面會(huì)出現(xiàn)一個(gè)BUG,就是左拉之后,點(diǎn)擊該商品對(duì)應(yīng)的商店下的編輯按鈕,刪除的按鈕會(huì)繼續(xù)被左拉,呈現(xiàn)一個(gè)比其他刪除按鈕長(zhǎng)的BUG狀態(tài)?! ?/p>
處理辦法:通過(guò)給每個(gè)商品的一個(gè)具有唯一識(shí)別性的ref,然后在點(diǎn)擊編輯時(shí),找到已左拉的商品的對(duì)應(yīng)的具有唯一識(shí)別性的ref,把它的左拉狀態(tài)還原即可,具體代碼如下所示:
shop.editingStatus=!shop.editingStatus if (shop.editingStatus){ shop.goodsList.forEach((good,index)=>{ if (good.touchDelete){ good.touchDelete=false this.$refs[`goods-${shopIndex}-${index}`][0].style.left='0px' } }) }
六、個(gè)人中心地址管理頁(yè)面
最后是個(gè)人中心地址管理頁(yè)面,在這個(gè)頁(yè)面中,我們會(huì)封裝addressService層和fetch層,addressService層主要是負(fù)責(zé)頁(yè)面中前后端交互的方法,如添加地址、刪除地址、編輯地址和獲取地址等,然后fetch層主要是負(fù)責(zé)從RAP接口中獲取數(shù)據(jù)并返回一個(gè)promise對(duì)象到service層中,具體的封裝方式和使用方式請(qǐng)自行查看源碼。
另外在這個(gè)頁(yè)面中,我們使用到了vue-router和vuex,接下來(lái)我將會(huì)簡(jiǎn)要介紹它們?cè)趥€(gè)人中心地址管理頁(yè)面中的使用方式。
首先是vue-router,他是用于構(gòu)建單頁(yè)面應(yīng)用的,是基于路由和組件,路由用于訪問(wèn)特定的路徑,然后特定的路徑與特定的組件相聯(lián)系相映射,傳統(tǒng)頁(yè)面中,是通過(guò)超鏈接來(lái)實(shí)現(xiàn)頁(yè)面的跳轉(zhuǎn)和切換的,但在vue-router中,則是路由的切換,即組件的切換。
我們先來(lái)看看是如何配置一個(gè)routes、創(chuàng)建一個(gè)router實(shí)例并把它注入到vue實(shí)例中去的:
import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) //構(gòu)造配置 let routes=[{ path:'/', components: require('../components/member.vue') },{ path:'/address', components:require('../components/address.vue'), children:[{ path:'', redirect: 'all' },{ path:'all', components:require('../components/all.vue') },{ path:'form', name:'form', components:require('../components/form.vue') }] }] //創(chuàng)建router實(shí)例 let router=new Router({ routes }) export default router import Vue from 'vue' import router from './router' import store from './vuex' //根組件注入 let view=new Vue({ el:'#app', router, store }) //router-view標(biāo)簽作為配置路由后組件的容器 <div id="app"> <router-view></router-view> </div>
通過(guò)這樣路由的配置和注入,我們就可以實(shí)現(xiàn)單頁(yè)面下多組件的切換和嵌套了,如果上述有不懂的地方,請(qǐng)到vue-router的官網(wǎng)處查看文檔和說(shuō)明。
接著我們來(lái)講一下vuex,vuex是對(duì)SPA即單頁(yè)面應(yīng)用進(jìn)行數(shù)據(jù)的狀態(tài)管理,如果想了解具體vuex是什么還有它的用途,請(qǐng)點(diǎn)擊這篇文章:Vuex新手入門指南
vuex其實(shí)也是組件間通信的一種方式,說(shuō)起組件間的通信,我們不如來(lái)一一列舉一下他們的方式有哪些:
1.引用類型數(shù)據(jù)
用法:如果父組件有一個(gè)數(shù)據(jù)類型是引用類型的數(shù)據(jù),當(dāng)這個(gè)數(shù)據(jù)直接傳遞給子組件以后,在子組件對(duì)這個(gè)數(shù)據(jù)源進(jìn)行修改的時(shí)候,父組件中該數(shù)據(jù)也會(huì)同步修改。
2.自定義事件
即子組件內(nèi)部定義了一個(gè)自定義事件,可以用父組件在子組件上進(jìn)行監(jiān)聽:
//子組件 this.$emit('change',18) //父組件 <foo :obj="obj" @change="changeAge"></foo> //父組件 methods:{ changeAge(age){ this.obj.age=age } }
3.全局事件(global bus)
//bus.js import Vue from 'vue' const bus=new Vue() export default bus //觸發(fā)組件 import bus from 'js/bus.js' bus.$emit('change',18) //訂閱組件 import bus from 'js/bus.js' bus.$on('change',(age)=>{ this.obj.age=age })
4.vuex狀態(tài)管理
vuex的使用與vue-router有一點(diǎn)相似,具體代碼如下:
import Vue from 'vue' //使用vuex插件 import Vuex from 'vuex' Vue.use(Vuex) import address from 'js/addressService.js' //創(chuàng)建Store實(shí)例 const store=new Vuex.Store({ state:{ lists:null }, mutations: { init(state,lists){ state.lists=lists } }, actions: { getLists({commit}){ address.getList().then(response=>{ commit('init',response.data.lists) }) } } }) export default store
之后同樣的在跟組件對(duì)store實(shí)例進(jìn)行注入即可,在上述實(shí)例中,state屬性表示的是實(shí)例的狀態(tài),類似vue實(shí)例里的data,需要高度注意的是,不允許直接修改state里面的值,只允許定義一系列的類似事件的mutations來(lái)觸發(fā)進(jìn)行state的管理。而mutations屬性里面存放的是同步事件,因此是對(duì)數(shù)據(jù)進(jìn)行同步管理,要進(jìn)行異步操作的話必須使用actions屬性;actions屬性里面存放一些異步的操作,在異步的操作進(jìn)行完成之后再觸發(fā)mutations里面的同步事件來(lái)對(duì)state里面的數(shù)據(jù)的狀態(tài)進(jìn)行同步的操作。
在組件中,我們一般通過(guò)dispatch來(lái)觸發(fā)actions里面的異步事件進(jìn)行異步操作,一般使用計(jì)算屬性來(lái)獲取state中的數(shù)據(jù),之所以使用計(jì)算屬性,是因?yàn)闋顟B(tài)管理里的數(shù)據(jù)可能是變化的,因此我們希望它在頁(yè)面中是響應(yīng)式的,因此我們選擇使用計(jì)算屬性來(lái)對(duì)數(shù)據(jù)進(jìn)行依賴的綁定。
具體代碼如下:
computed:{ list(){ if(this.$store.state.lists){ return this.$store.state.lists } return false } }, created(){ if (!this.list){ //防止在新增地址或修改地址后多次觸發(fā)mutations中的init this.$store.dispatch('getList') } }
總之,vuex中狀態(tài)管理的過(guò)程可總結(jié)為以下流程:
(1).通過(guò)dispatch(actionFnName)分發(fā)來(lái)觸發(fā)actions中的異步操作=>
(2).待異步操作結(jié)束之后通過(guò)commit(mutationsFnName,data)來(lái)觸發(fā)mutations中的同步事件來(lái)進(jìn)行同步操作=>
(3).通過(guò)同步操作改變state中的數(shù)據(jù)的狀態(tài)=>
(4).狀態(tài)改變后,組件中的計(jì)算屬性因?yàn)榻壎嗽摂?shù)據(jù)作為依賴,因此數(shù)據(jù)的改變會(huì)響應(yīng)式地展示在頁(yè)面中,即頁(yè)面展示的數(shù)據(jù)也會(huì)得到同步的改變
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue項(xiàng)目創(chuàng)建并引入餓了么elementUI組件的步驟
這篇文章主要介紹了vue項(xiàng)目創(chuàng)建并引入餓了么elementUI組件的步驟,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-04-04淺談vue實(shí)現(xiàn)雙向事件綁定v-model的原理
vue使用v-model實(shí)現(xiàn)數(shù)據(jù)的雙向綁定,它會(huì)根據(jù)控件類型自動(dòng)選取正確的方法來(lái)更新元素,本文就詳細(xì)的介紹一下原理,感興趣的可以了解一下2021-08-08使用vue-aplayer插件時(shí)出現(xiàn)的問(wèn)題的解決
這篇文章主要介紹了使用vue-aplayer插件時(shí)出現(xiàn)的問(wèn)題的解決,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03基于Element-Ui封裝公共表格組件的詳細(xì)圖文步驟
在平時(shí)開發(fā)的時(shí)候很多情況都會(huì)使用到表格和分頁(yè)功能,下面這篇文章主要給大家介紹了關(guān)于如何基于Element-Ui封裝公共表格組件的詳細(xì)圖文步驟,需要的朋友可以參考下2022-09-09安裝nvm?node版本管理器的操作方法(vue2.x遷移vue3.x)
這篇文章主要介紹了安裝nvm?node版本管理器(vue2.x遷移vue3.x)的操作方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-01-01