這個是學習複雜的組件的封裝的,在課程中,主要實現的是書單上方的搜索功能組件的開發,這個應該是較以前的組件是有必定難度的,可是如今學到如今,感受前端的內容和後端的內容比較起來,仍是比較容易的,並且好多內容,其實在後端的開發中是很成熟的,因此學起來並非很難以理解,這也是咱們的一個優點吧,畢竟選擇後端的同窗應該是不錯的啊,哈哈哈!這種組件實際上是有一個特別的名字的,那就是高階組件,來,把這個東西儘快學習掌握!html
1、新建search組件前端
首先仍是新建search組件的各個文件,這裏微信開發者工具的功能很好用,直接就新建一個search目錄,而後在目錄中新建一個名字爲index的component組件,這樣就建好了四個與組件相關的文件web
2、search組件開發算法
一、基礎的結構搭建小程序
(1)樣式文件的代碼index.wxml文件後端
1 <view class="container"> 2 <view class="header"> 3 <view class="search-container"> 4 <image class="icon" src="images/search.png"></image> 5 <input placeholder-class="in-bar" placeholder="書籍名" class="bar" auto-focus="true"></input> 6 <image class="cancel-img" src="images/cancel.png"></image> 7 </view> 8 <view class="cancel" bindtap="onCancel">取消</view> 9 </view> 10 <view> 11 <view class="history"> 12 <view class="title"> 13 <view class="chunk"></view> 14 <text>歷史搜索</text> 15 </view> 16 </view> 17 <view class="history hot-search"> 18 <view class="title"> 19 <view class="chunk"></view> 20 <text>熱門搜索</text> 21 </view> 22 </view> 23 </view> 24 </view>
(2)樣式文件 index.wxss代碼緩存
1 .container { 2 display: flex; 3 flex-direction: column; 4 align-items: center; 5 width: 100%; 6 /* padding-left:15px; *//* padding-right:15px; */ 7 } 8 9 .history { 10 width: 690rpx; 11 margin: 40rpx 0 20rpx 0; 12 display: flex; 13 font-size: 14px; 14 margin-top:160rpx; 15 flex-direction: column; 16 } 17 18 .hot-search{ 19 margin-top:70rpx; 20 } 21 22 .title { 23 line-height: 15px; 24 display: flex; 25 flex-direction: row; 26 align-items: center; 27 /* margin-left:100px; */ 28 } 29 30 .search-container { 31 display: inline-flex; 32 flex-direction: row; 33 align-items: center; 34 background-color: #f5f5f5; 35 border-radius: 50px; 36 margin-left: 20rpx; 37 /* margin-left: */ 38 } 39 40 .books-container book-cmp { 41 margin-bottom: 25rpx; 42 } 43 44 .cancel-img { 45 width: 14px; 46 height: 14px; 47 margin-right: 10px; 48 } 49 50 .books-container { 51 width: 570rpx; 52 margin-top:100rpx; 53 display: flex; 54 flex-direction: row; 55 flex-wrap: wrap; 56 padding: 0 90rpx 0 90rpx; 57 justify-content: space-between; 58 } 59 60 .loading { 61 margin: 50rpx 0 50rpx 0; 62 } 63 64 .loading-center { 65 position: absolute; 66 top: 50%; 67 left: 50%; 68 } 69 70 .empty-tip { 71 display: inline-block; 72 width: 100%; 73 text-align: center; 74 position: absolute; 75 top: 50%; 76 /* left: 275rpx; */ 77 } 78 79 .icon { 80 width: 14px; 81 height: 14px; 82 margin-left: 12px; 83 margin-right: 8px; 84 } 85 86 .in-bar { 87 color: #999; 88 } 89 90 .cancel { 91 line-height: 34px; 92 width: 60px; 93 /* margin-left:10px; */ 94 text-align: center; 95 display: inline-block; 96 border: none; 97 } 98 99 .chunk { 100 height: 15px; 101 width: 5px; 102 background-color: #000; 103 display: inline-block; 104 margin-right: 10px; 105 } 106 107 .tags { 108 /* padding-left:15px; */ 109 display: flex; 110 flex-direction: row; 111 flex-wrap: wrap; 112 /* justify-content: flex-start; */ 113 margin-top: 24rpx; 114 padding-left: 15px; 115 width: 630rpx; 116 } 117 118 .tags tag-cmp { 119 margin-right: 10px; 120 margin-bottom: 10px; 121 /* padding-bottom: 10px; *//* margin-right:6px; */ 122 } 123 124 .header { 125 background-color: #ffffff; 126 position:fixed; 127 height: 100rpx; 128 border-top: 1px solid #f5f5f5; 129 border-bottom: 1px solid #f5f5f5; 130 display: flex; 131 flex-direction: row; 132 width: 750rpx; 133 align-items: center; 134 z-index:99; 135 /* padding-left:15px; *//* padding-right:5px; */ 136 } 137 138 .bar { 139 border-top-right-radius: 15px; 140 border-bottom-right-radius: 15px; 141 display: inline-block; 142 height: 34px; 143 /* width:100%; */ 144 width: 500rpx; 145 font-size: 14px; 146 } 147 148 .test { 149 background-color: #000; 150 }
(3)基礎的業務邏輯處理 index.js服務器
這個主要是將取消操做交給page中頁面進行處理,將組件中的取消事件傳遞給page頁面中微信
1 /** 2 * 組件的方法列表 3 */ 4 methods: { 5 // 搜索取消事件 6 onCancel(event){ 7 this.triggerEvent('cancel',{},{}); 8 } 9 }
page中book頁面進行組件的展現以及業務邏輯處理的代碼:微信開發
book.wxml文件中添加事件以及顯示代碼
1 <view class="container" wx:if="{{!searching}}"> 2 <view class="header"> 3 <view class="box" bindtap="onSearching"> 4 <image src="/images/icon/search.png"></image> 5 <text>搜索書籍</text> 6 </view> 7 </view> 8 <view class="sub-container"> 9 <image src="/images/book/quality.png" class="head-img"></image> 10 <view class="books-container"> 11 <block wx:key="id" wx:for="{{books}}"> 12 <v-book book="{{item}}" /> 13 </block> 14 </view> 15 </view> 16 </view> 17 <!-- search組件的使用 --> 18 <v-search bind:cancel="onCancel" wx:if="{{searching}}"></v-search>
book.js中添加部分處理方法
1 // 添加searching屬性 2 data: { 3 // 服務器請求的數據 book的集合 4 books:[], 5 searching:false 6 }, 7 8 // 搜索框的點擊事件 9 onSearching(event){ 10 this.setData({ 11 searching:true 12 }) 13 }, 14 15 // 搜索框取消事件 16 onCancel(event){ 17 this.setData({ 18 searching:false 19 }) 20 },
二、組件中的業務代碼
如今不知道從哪裏開始寫起了,昨天本身動手寫了一下,簡單的實現了搜索的功能,把前兩天學習的內容簡單的記錄一下。
(1)搜索記錄的標籤顯示
這個是分爲兩種搜索標籤的,一種是歷史搜索,一種是熱門搜索,這兩種實現方式是不一樣的,歷史搜索是從緩存中加載保存的用戶搜索記錄,這個是有總數限制的,很值得學習一下這種方案的處理思路,熱門搜索就是從服務器加載熱門搜索記錄,這個就有灰色空間了,若是數據量很是大的時候,這個時候會用到排序算法了,以前學習過,具體怎麼實現,如今也是沒有記住,整體的思路仍是在大腦中有點的,看看這兩種的實現:
1 <view wx:if="{{!searching}}"> 2 <view class="history"> 3 <view class="title"> 4 <view class="chunk"></view> 5 <text>歷史搜索</text> 6 </view> 7 <view class="tags"> 8 <block wx:for="{{historyWords}}" wx:key=""> 9 <v-tag text="{{item}}" bind:tapping="onConfirm" /> 10 </block> 11 </view> 12 </view> 13 <view class="history hot-search"> 14 <view class="title"> 15 <view class="chunk"></view> 16 <text>熱門搜索</text> 17 </view> 18 <view class="tags"> 19 <block wx:for="{{hotWords}}" wx:key=""> 20 <v-tag text="{{item}}" bind:tapping="onConfirm" /> 21 </block> 22 </view> 23 </view> 24 </view>
上面是頁面展現的實現,下面看一下具體的邏輯實現:
這個是新建的keyword.js文件,在models文件夾下面,主要是有幾個相關的方法,重點是關注一下addToHistroy方法的
1 import {HTTP} from '../util/http-p.js' 2 class KeywordModel extends HTTP{ 3 key = "q"; // 緩存中的key 4 maxLength = 10; // 歷史搜索展現的條數 5 // 獲取歷史搜索方法 6 getHistory(){ 7 const words = wx.getStorageSync(this.key); 8 if(!words){ 9 return []; 10 } 11 return words; 12 } 13 14 // 獲取熱門的方法 15 getHot(){ 16 return this.request({ 17 url:'/book/hot_keyword' 18 }) 19 } 20 21 // 將搜索關鍵字寫入緩存中 22 addToHistory(keyword){ 23 // 注意緩存中是一組數據 24 let words = this.getHistory(this.key); 25 const has = words.includes(keyword); 26 if(!has){ 27 const length = words.length; 28 // 刪除末尾的Word 29 if(length >= this.maxLength){ 30 words.pop(); 31 } 32 words.unshift(keyword); 33 wx.setStorageSync(this.key, words); 34 } 35 } 36 } 37 38 export { KeywordModel }
下面是search組件中的index.js文件中的具體邏輯實現,主要就是在search組件加載的時候,初始化這個歷史搜索與熱門搜索的標籤,這個是在attached函數中,這個attached方法是小程序中的默認的組件加載時執行的方法
1 /** 2 * 組件的初始數據 3 */ 4 data: { 5 historyWords:[], 6 hotWords:[], 7 dataArray:[], 8 searching:false, 9 q:"" 10 }, 11 12 // 組件初始化時候調用的方法 13 attached(){ 14 this.setData({ 15 historyWords: keywordModel.getHistory() 16 }) 17 18 keywordModel.getHot().then(res => { 19 this.setData({ 20 hotWords:res.hot 21 }) 22 }) 23 },
(2)書籍信息的顯示
這個如今只是實現了書籍信息的簡單展現,沒有實現分頁的操做,後續的會實現這個功能
1 <!-- 書籍展現 --> 2 <view class="books-container" wx:if="{{searching}}"> 3 <block wx:for="{{dataArray}}" wx:key="{{item.id}}"> 4 <v-book book="{{item}}" class="book"></v-book> 5 </block> 6 </view>
這個頁面展現的代碼就比較簡單了,咱們只是複用了一下book組件,因此這裏實現起來就比較簡單了,下面是邏輯代碼,主要就是調用接口加載數據,還有就是對數據的一些處理,以及一些具體細節的處理,這個細節的處理很容易被忽視的,可是這些東西纔是體現一個項目的好壞,一個開發者好壞的真正的東西
1 // 用戶搜索的方法 2 onConfirm(event){ 3 this.setData({ 4 searching:true 5 }) 6 const word = event.detail.value || event.detail.text; 7 bookModel.search(0, word).then(res => { 8 console.log(res); 9 this.setData({ 10 dataArray:res.books, 11 q: word 12 }) 13 keywordModel.addToHistory(word); 14 }) 15 },
(3)搜索結果的分頁加載
這個業務場景是當用戶搜索結果展現出來的時候,以前只是展現若干條數據,沒法所有顯示搜索結果,這種作法固然無可厚非,可是咱們要進一步完善這個功能,那就有必要來實現分頁功能了,當用戶下滑到底部的時候,若是還有數據,那麼咱們須要加載出來,那麼這個如何實現,哈哈
具體思路:
(1)在page中小程序是有事件來實現這個下拉觸發動做的,那就是onReachBottom事件,如何將這個動做的通知傳遞到組件中,讓組件接收到這個通知,實現具體的邏輯
(2)能夠經過組件的properties屬性來傳遞這個通知,屬性中監聽函數observer來實現處理邏輯,這裏observer監聽 函數只有當屬性值改變的時候纔會觸發,因此,咱們的解決辦法是每次傳遞一個隨機數給屬性,讓每一次通知都能被組件接收
(3)剩下的就是具體的邏輯處理了,這裏面有好多細節須要處理的,具體看代碼
首先,看一下page中 book.wxml以及book.js中的代碼
1 // wxml中代碼 簡寫省略其餘 2 <!-- search組件的使用 --> 3 <v-search more="{{more}}" bind:cancel="onCancel" wx:if="{{searching}}"></v-search> 4 5 // js中代碼 6 data: { 7 // 服務器請求的數據 book的集合 8 books:[], 9 searching:false, 10 more:'' // 是否加載更多數據 11 }, 12 13 onReachBottom: function() { 14 // console.log("aaaa"); 15 // 加載更多數據 16 this.setData({ 17 more:random(16) 18 }) 19 },
這裏有一個產生隨機數的方法,random(16) 產生16位的隨機數,很簡單,不貼代碼了
看一下search組件中的相關代碼,主要是增長了一個屬性,增長了loading ,這個充當的是鎖的角色,這個方法還有待優化,
說一下這裏面的細節處理:
loading這個鎖的引入,防止用戶下拉觸發事件過於頻繁,向服務器發送過多請求,致使的信息加載出現重疊的問題,影響服務器的性能,引入loading鎖以後,只有一個請求發送完畢以後,接下來的請求才能繼續發送,這個鎖的概念在多線程中應用的很普遍,做爲後端開發,這個問題很容易理解!
1 properties: { 2 more:{ 3 type:String, 4 observer:'_load_more' 5 } 6 }, 7 8 data: { 9 loading:false 10 }, 11 12 // 加載更多數據 13 _load_more(){ 14 console.log(123123); 15 if(!this.data.q){ 16 return; 17 } 18 // loading在這裏扮演的是鎖的角色 19 if(this.data.loading){ 20 return; 21 } 22 const length = this.data.dataArray.length; 23 this.data.loading = true 24 bookModel.search(length,this.data.q).then(res => { 25 const tempArray = this.data.dataArray.concat(res.books); 26 this.setData({ 27 dataArray:tempArray 28 }) 29 this.data.loading = false 30 }) 31 },
(4)搜索代碼的優化
這個優化主要是設計到兩方面,一方面是代碼的抽離,一方面是代碼的可讀性
先看看代碼的抽離如何來優化,主要是將分頁的相關的代碼抽離成behavior行爲,而後直接在組件中引用behavior中的方法,新建一個behaviors文件夾,建立一個pagination.js文件
看一下pagination.js中的代碼:
1 const paginationBev = Behavior({ 2 data: { 3 dataArray: [], // 分頁數據 4 total: null 5 }, 6 methods: { 7 setMoreData(dataArray) { 8 const tempArray = this.data.dataArray.concat(dataArray); 9 this.setData({ 10 dataArray: tempArray 11 }) 12 }, 13 // 獲取當前開始的index值 14 getCurrentStart() { 15 return this.data.dataArray.length; 16 }, 17 18 setTotal(total) { 19 this.data.total = total; 20 }, 21 22 // 是否還有數據須要加載 23 hasMore() { 24 if (this.data.dataArray.length >= this.data.total) { 25 return false; 26 } else { 27 return true; 28 } 29 }, 30 initialize(){ 31 this.data.dataArray = []; 32 this.data.total = null; 33 } 34 } 35 }) 36 37 export { 38 paginationBev 39 }
看一下在組件中如何使用:
1 import { 2 KeywordModel 3 } from '../../models/keyword.js' 4 5 import { 6 BookModel 7 } from '../../models/book.js' 8 9 import { 10 paginationBev 11 } from '../behaviors/pagination.js' 12 13 Component({ 14 // 引入 組件中behaviors屬性 15 behaviors: [paginationBev], 16 /** 17 * 組件的屬性列表 18 */ 19 properties: { 20 more: { 21 type: String, 22 observer: 'loadMore' 23 } 24 }, 25 26 /** 27 * 組件的方法列表 28 */ 29 methods: { 30 // 加載更多數據 31 loadMore() { 32 if (!this.data.q) { 33 return; 34 } 35 // loading在這裏扮演的是鎖的角色 36 if (this._isLocked()) { 37 return; 38 } 39 40 if (this.hasMore()){ 41 this._locked(); 42 bookModel.search(this.getCurrentStart(), this.data.q).then(res => { 43 this.setMoreData(res.books); 44 this._unLocked(); 45 },()=>{ 46 // 避免死鎖 在請求失敗的時候也須要釋放鎖 47 this._unLocked(); 48 }) 49 } 50 }, 51 // 搜索取消事件 52 onCancel(event) { 53 this.triggerEvent('cancel', {}, {}); 54 }, 55 // 用戶搜索的方法 56 onConfirm(event) { 57 // 控制搜索結果的顯示 58 this._showResult(); 59 // 初始化behavior中的數據 60 this.initialize(); 61 const word = event.detail.value || event.detail.text; 62 bookModel.search(0, word).then(res => { 63 this.setMoreData(res.books); 64 this.setTotal(res.total); 65 this.setData({ 66 q: word 67 }) 68 keywordModel.addToHistory(word); 69 }) 70 }, 71 // X的圖標取消事件 72 onDelete(event) { 73 this._closeResult(); 74 }, 75 // 顯示搜索結果 76 _showResult(){ 77 this.setData({ 78 searching: true 79 }) 80 }, 81 // 隱藏搜索結果 82 _closeResult(){ 83 this.setData({ 84 searching: false 85 }) 86 }, 87 // 判斷是否有鎖 88 _isLocked(){ 89 this.data.loading?true:false; 90 }, 91 // 加鎖 92 _locked(){ 93 this.data.loading = true; 94 }, 95 // 釋放鎖 96 _unLocked(){ 97 this.data.loading = false; 98 } 99 }, 100 })
注意:帶有下劃線的方法是理論上的私有方法,姑且這麼說吧,其實本質上和其餘方法是一致的,這些方法的優化是增長代碼的可讀性,使得代碼更加容易讓人理解,這裏其實由不少細節須要注意的,包括鎖,爲了不死鎖,須要在請求失敗的時候同時將鎖釋放,以及confirm方法中主要將以前的數據清空,不然會形成dataArray中數據是重複數據,還有就是在加鎖的時候須要在判斷是否還有更多數據以後進行,若是在這以前進行,那麼會形成數據不會加載的狀況,等等,以後會完善一下加載圖標,哈哈,感受愈來愈完美
(5)loading組件的開發與應用
這個就直接從網上找一個loading圖標的樣式就行,看看loading組件的代碼
1 // index.wxml代碼 2 <view class="spinner"> 3 <view class="double-bounce1"></view> 4 <view class="double-bounce2"></view> 5 </view> 6 7 // 樣式代碼 index.wxss 8 .spinner { 9 width: 40rpx; 10 height: 40rpx; 11 position: relative; 12 /* margin: 100px auto; */ 13 } 14 15 .double-bounce1, .double-bounce2 { 16 width: 100%; 17 height: 100%; 18 border-radius: 50%; 19 background-color: #3063b2; 20 opacity: 0.6; 21 position: absolute; 22 top: 0; 23 left: 0; 24 25 -webkit-animation: bounce 2.0s infinite ease-in-out; 26 animation: bounce 2.0s infinite ease-in-out; 27 } 28 29 .double-bounce2 { 30 -webkit-animation-delay: -1.0s; 31 animation-delay: -1.0s; 32 } 33 34 @-webkit-keyframes bounce { 35 0%, 100% { -webkit-transform: scale(0.0) } 36 50% { -webkit-transform: scale(1.0) } 37 } 38 39 @keyframes bounce { 40 0%, 100% { 41 transform: scale(0.0); 42 -webkit-transform: scale(0.0); 43 } 50% { 44 transform: scale(1.0); 45 -webkit-transform: scale(1.0); 46 } 47 }
看一下loading組件的應用:
主要是在搜索結果展現以前,以及加載更多的時候進行loading組件的顯示,在其餘時候是無需顯示的
1 <!-- loading圖標顯示 --> 2 <v-loading class="loading-center" wx:if="{{loadingCenter}}" /> 3 <v-loading class="loading" wx:if="{{loading}}" />
看一下如何控制顯示隱藏的
loadingCenter主要是在onConfirm方法中進行控制的,這個很少說,看一下loading的控制,就是在加鎖和釋放鎖的時候進行控制就好了
1 // 加鎖 2 locked() { 3 // this.data.loading = true; 4 this.setData({ 5 loading: true 6 }) 7 }, 8 // 釋放鎖 9 unLocked() { 10 // this.data.loading = false; 11 this.setData({ 12 loading: false 13 }) 14 }
這裏還有代碼的優化,以及在沒有搜索結果的時候進行友好的提示,以及在沒有更多的數據的時候進行友好的提示,以及在取消的時候進行數據的初始化操做,不少細節的東西,這裏就不想寫了,很瑣碎的東西,可是在我看來是很值得付出時間去完善的一部分,細節決定成敗,你們一樣是一個功能,最能看出一我的水平的是誰能把細節注意到,而且可以作好,在之後的工做中,這是本身須要提高的一個領域,專一細節,佈局整個系統