微信小程序之組件的集合(五)

  這個是學習複雜的組件的封裝的,在課程中,主要實現的是書單上方的搜索功能組件的開發,這個應該是較以前的組件是有必定難度的,可是如今學到如今,感受前端的內容和後端的內容比較起來,仍是比較容易的,並且好多內容,其實在後端的開發中是很成熟的,因此學起來並非很難以理解,這也是咱們的一個優點吧,畢竟選擇後端的同窗應該是不錯的啊,哈哈哈!這種組件實際上是有一個特別的名字的,那就是高階組件,來,把這個東西儘快學習掌握!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     }

這裏還有代碼的優化,以及在沒有搜索結果的時候進行友好的提示,以及在沒有更多的數據的時候進行友好的提示,以及在取消的時候進行數據的初始化操做,不少細節的東西,這裏就不想寫了,很瑣碎的東西,可是在我看來是很值得付出時間去完善的一部分,細節決定成敗,你們一樣是一個功能,最能看出一我的水平的是誰能把細節注意到,而且可以作好,在之後的工做中,這是本身須要提高的一個領域,專一細節,佈局整個系統

 

內容出處:七月老師《純正商業級小程序開發》視頻課程
相關文章
相關標籤/搜索