zoukankan      html  css  js  c++  java
  • 微信小程序之组件的集合(五)

      这个是学习复杂的组件的封装的,在课程中,主要实现的是书单上方的搜索功能组件的开发,这个应该是较之前的组件是有一定难度的,但是现在学到现在,感觉前端的内容和后端的内容比较起来,还是比较容易的,而且好多内容,其实在后端的开发中是很成熟的,所以学起来并不是很难以理解,这也是我们的一个优势吧,毕竟选择后端的同学应该是不错的啊,哈哈哈!这种组件其实是有一个特别的名字的,那就是高阶组件,来,把这个东西尽快学习掌握!

    一、新建search组件

    首先还是新建search组件的各个文件,这里微信开发者工具的功能很好用,直接就新建一个search目录,然后在目录中新建一个名字为index的component组件,这样就建好了四个与组件相关的文件

     二、search组件开发

    1、基础的结构搭建

    (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   /* 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   },

    2、组件中的业务代码

     现在不知道从哪里开始写起了,昨天自己动手写了一下,简单的实现了搜索的功能,把前两天学习的内容简单的记录一下。

    (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    40rpx;
    10   height: 40rpx;
    11   position: relative;
    12   /* margin: 100px auto; */
    13 }
    14  
    15 .double-bounce1, .double-bounce2 {
    16    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     }

    这里还有代码的优化,以及在没有搜索结果的时候进行友好的提示,以及在没有更多的数据的时候进行友好的提示,以及在取消的时候进行数据的初始化操作,很多细节的东西,这里就不想写了,很琐碎的东西,但是在我看来是很值得付出时间去完善的一部分,细节决定成败,大家同样是一个功能,最能看出一个人水平的是谁能把细节注意到,并且能够做好,在以后的工作中,这是自己需要提升的一个领域,专注细节,布局整个系统

    内容出处:七月老师《纯正商业级小程序开发》视频课程
  • 相关阅读:
    svn Mac
    webpack实用配置
    vuex状态管理-数据改变不刷新
    element-vue-koa2-mysql实现文件上传
    Promise的理解
    mysql Mac篇
    python 24 days
    python 7 days
    python 27 days
    python 26 days
  • 原文地址:https://www.cnblogs.com/ssh-html/p/11442898.html
Copyright © 2011-2022 走看看