这个是学习复杂的组件的封装的,在课程中,主要实现的是书单上方的搜索功能组件的开发,这个应该是较之前的组件是有一定难度的,但是现在学到现在,感觉前端的内容和后端的内容比较起来,还是比较容易的,而且好多内容,其实在后端的开发中是很成熟的,所以学起来并不是很难以理解,这也是我们的一个优势吧,毕竟选择后端的同学应该是不错的啊,哈哈哈!这种组件其实是有一个特别的名字的,那就是高阶组件,来,把这个东西尽快学习掌握!
一、新建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 /* 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 },
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 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 }
这里还有代码的优化,以及在没有搜索结果的时候进行友好的提示,以及在没有更多的数据的时候进行友好的提示,以及在取消的时候进行数据的初始化操作,很多细节的东西,这里就不想写了,很琐碎的东西,但是在我看来是很值得付出时间去完善的一部分,细节决定成败,大家同样是一个功能,最能看出一个人水平的是谁能把细节注意到,并且能够做好,在以后的工作中,这是自己需要提升的一个领域,专注细节,布局整个系统
来源:oschina
链接:https://my.oschina.net/u/4419222/blog/3392177