开发实战篇之一
- 前言
- 1、通用方法的封装
- 2、数据获取方法的封装
- 2.1 抽离classic.js中的request()方法
- 2.2 组件数据传递
- 3、movie组件的创建
- 3.1 组件代码开发
- 3.2 classic.wxml应用组件
- 4、自定义事件的应用,传递like组件状态
- 4.1 在likes组件中定义behavior状态:like和cancel;然后以islike事件,把behavior传递给页面classic。
- 4.2 页面绑定islike事件并监听onClickLike事件。
- 4.3 封装request向服务器传递状态函数。
- 5、开发期刊组件episode
- 5.1 设置episode.js中的属性及内部数据
- 5.2 创建页面及css样式
- 5.3 在展示页面引入
- 6、开发导航组件navi
- 6.1 导航组件的属性和数据
- 6.2 导航组件的布局和样式
- 6.3 导航组件的应用
- 6.4 导航组件的左右切换func
- 7、music、essay组件开发并完善(behavior)
- 7.1 music组件
- 7.2 essay组件(文章组件)
- 8、完善并优化-导航栏组件的切换
- 8.1 将判断latest、first期刊号方法,封装进model的classicModel中
- 8.2 将后台获取上一期、下一期内容方法,同样封装进classicModel中
- 8.3 在页面classic.js中使用classicModel定义方法
- 9、onNext和onPrevious方法重构
- 9.1 classicModel中的getNext()和getPrevious()整合成getClassic(x,x,x)方法
- 9.2 页面classic.js中的getNext()和getPrevious()整合成私有方法_updateClassic(...)
- 10、getClassic()方法使用本地缓存(提高select效率各方面)
前言
实战篇内容参考:
1、小程序开发实战:https://coding.imooc.com/class/chapter/251.html#Anchor
2、ssc在路上博主
1、通用方法的封装
这个主要是来优化请求的方法,我们把一些通用的数据抽离出来,来写成公共的方法,供许多模块调用。config.js像java中的properties文件。
ES6语法中一个js就是一个模块,官方导入js使用require,但可以使用导入module的方式。
(1)封装的config.js代码:
// export导出config
// const关键字是声明不变的值的时候来用的,相当于Java中静态变量
export const config = {
api_base_url: 'http://xxx,
appkey: 'K0LDaSADSDLWWbF'
}
// 导出function函数方法
export let fun1 = function(){ }
// export {const, fun1} 这种写法就不用每一个都写export关键字了,一定要使用花括号包裹
(2)ES6封装HTTP类:
// 导入的时候需要使用相对路径
import {config} from '../config.js';
// 使用别名,导入其他函数
//import {config as cfg, fun1} from '../config.js';
// 错误的具体的提示信息 根据接口中error_code
const tips = {
1:'抱歉,出现了一个错误',
1005:'appKey不正确',
3000:'期刊不存在'
}
class HTTP{
request(params){
if(!params.method){
params.method = 'GET';
}
wx.request({
url: config.api_base_url + params.url,
method:params.method,
data:params.data,
header:{
'content-type':'application/json',
'appkey': config.appkey
},
success:(res) => {
// 来判断请求是否成功 以2开头就是成功 这个是在Number类型的, // 需要装换成string类型
var code = res.statusCode.toString();
// ES6中 startsWith 和 endsWith
if(code.startsWith('2')){
params.sucess(res.data);
}else{
// 错误信息的提示
var error_code = res.data.error_code;
this._show_error(error_code);
}
},
fail:(err) => { //api调用失败
this.error_code(1);
}
})
}
// 错误信息的提示方法
_show_error(error_code){
if(!error_code){
error_code = 1;
}
wx.showToast({
title: tips[error_code],
icon: 'none',
duration: 2000
})
}
}
// 使得类外部可以访问
export {HTTP}
(3)classic.js使用http.js
// pages/classic/classic.js
import {HTTP} from '../../util/http.js'
let http = new HTTP()
...
onLoad: function (options) {
http.request({
url: 'classic/latest',
success:(res) => {
console.log(res.data)
}
})
},
...
2、数据获取方法的封装
上述文章将http请求和常用配置封装,继续获取数据的方法也封装起来。
2.1 抽离classic.js中的request()方法
创建models文件夹,来将classic.js中的代码继续分离出来,来创建一个classicModel类。
进一步进行代码的抽离,代码如下:
// model文件夹下classic.js代码如下
import{
HTTP
}from '../util/http.js'
// 新建ClassicModel类来从服务器获取数据
class ClassicModel extends HTTP {
getLatest(sCallBack) {
this.request({
url: 'classic/latest',
success: (res) => {
// 调用回调函数 来传递数据!!!
sCallBack(res);
}
})
}
}
// export ClassicModel
export {
ClassicModel
}
注意:这里的
getLatest()
函数是一个异步函数,我们无法直接获取它的返回值。必须通过success()
中调用回调函数sCallBack(res)
传递数据!
2.2 组件数据传递
无法使用组件的data属性,因为该属性是私有的。
classic文件夹中classic.wxml页面中调用组件的时候。可以把数据存放到组件的属性中,利用数据绑定来传递数据
<v-like like='{{classic.like_status}}' count='{{classic.fav_nums}}' />
注意:这里面的 this.setData({classic: res}) 这个是用来做数据更新的,当data中的数据改变之后,只有通过setData才能进行数据的更新。
3、movie组件的创建
3.1 组件代码开发
组件wxml
<!--pages/classic/movie/index.wxml-->
<view class="classic-container">
<image class="classic-img" src="{{img}}"></image>
<image class="tag" src="images/movie@tag.png"></image>
<text class="content">{{content}}</text>
</view>
组件样式
/* pages/classic/movie/index.wxss */
.classic-container {
display: flex;
flex-direction: column;
align-items: center;
}
.classic-img{
width: 750rpx;
height: 500rpx;
}
.tag {
width: 46rpx;
height: 142rpx;
position: relative;
right: 310rpx;
bottom: 58rpx;
}
.content {
font-size: 36rpx;
max-width: 550rpx;
}
组件js
properties: {
img: String,
content: String
},
3.2 classic.wxml应用组件
// classic.json
{
"usingComponents": {
"v-like":"/components/like/index",
"v-movie": "/components/classic/movie/index"
}
}
// classic.wxml文件中加入组件标签
<v-movie img='{{classic.image}}' content='{{classic.content}}' />
4、自定义事件的应用,传递like组件状态
这个主要是在点赞功能的上面进行的进一步的完善的功能,我们是如何将点赞的状态发送给服务端的,在这里最好的解决办法是在组件中进行自定义事件的开发。
4.1 在likes组件中定义behavior状态:like和cancel;然后以islike事件,把behavior传递给页面classic。
// 自定义事件 传递一个string
var behavior = this.properties.isLike?'like':'cancel';
this.triggerEvent('islike',{behavior: behavior},{});
4.2 页面绑定islike事件并监听onClickLike事件。
<v-like bind:islike="onClickLike" isLike="{{classic.like_status}}" count="{{classic.fav_nums}}" ></v-like>
在调用like组件的页面classic.js声明onClickLike函数。
- 在事件e中detail中可获取triggerEvent传递的参数behavior;
- 调用封装的likeModel对象的like(String behaivor, String id, String type)函数。
(behavior表示喜欢与否,id表示喜欢的东西的id号,type表示该物体的类型)
onClickLike: function(e){
console.log(e);
var behavior = e.detail.behavior;
likeModel.like(behavior, this.data.classic.id, this.data.classic.type);
},
4.3 封装request向服务器传递状态函数。
新建like.js 文件在modules文件夹下面,这个文件主要是创建LikeModel,将数据的更新方法写到这个model中,使得代码更好维护,模块化更加清楚。
like.js将request函数进行封装,然后对外暴露接口。
import {
HTTP
} from '../util/http.js'
// 创建LikeModel继承HTTP类
class LikeModel extends HTTP {
like(behavior, artId, category) {
var url = behavior == 'like' ? 'like' : 'like/cancel';
this.request({
url: url,
type: 'POST',
data: {
art_id: artId,
type: category
}
})
}
}
export {
LikeModel
}
// classic.js显示likes组件
// 导入封装函数
import {
ClassicModel
} from '../../models/classic.js'
import {
LikeModel
} from '../../models/like.js';
// 实例化HTTP类
var classic = new ClassicModel();
var likeModel = new LikeModel();
Page({
/**
* 页面的初始数据
*/
data: {
classic: null
},
// 自定义的onClickLike事件
onClickLike: function(e){
console.log(e);
var behavior = e.detail.behavior;
likeModel.like(behavior, this.data.classic.id, this.data.classic.type);
},
5、开发期刊组件episode
分析: 期刊号数据需要由页面传入组件,发行日期是组件内部数据。
5.1 设置episode.js中的属性及内部数据
这里需要注意:
- properties中的属性值,data中的数据名不能相同。不然小程序会覆盖数据!
- 分清业务逻辑的归属问题,属于组件还是page?不要将逻辑都糅合到page里。
(1)发行日期直接在组件初始化时,使用时间函数获取即可。
这里更新data,组件数据时,需要使用到组件生命周期函数attached,该函数是在组件初始化时可以调用的。
(2)刊号index由页面外部传入,使用observer函数监听外部变化,然后更新组件内_index数据(考虑到不可以直接修改index属性,不然会导致死循环调用observer函数,产生内存溢出)
在组件完全初始化完毕、进入页面节点树后, attached 生命周期被触发。此时, this.data 已被初始化为组件的当前值。这个生命周期很有用,绝大多数初始化工作可以在这个时机进行。
详细:https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/lifetimes.html
// components/epsoide/index.js
Component({
/**
* 组件的属性列表 属性中的变量不能和data中的变量同名
*/
properties: {
// index: Number
index: {
type: Number,
observer: function(newVal, oldVal, changedPath){ // 监听者模式
let val = newVal < 10?'0'+ newVal:newVal
this.setData({
_index: val
})
}
}
},
/**
* 组件的初始数据
*/
data: {
// data中数据的赋值,不能像properties中那样,直接给初始值就行
months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
year: 0,
month: '',
_index: ''
},
// 组件生命周期函数attached 在组件实例进入页面节点树时执行,还没渲染页面
attached: function(){
let date = new Date();
let year = date.getFullYear();
let month = date.getMonth();
this.setData({
year: year,
month: this.data.months[month]
})
},
/**
* 组件的方法列表
*/
methods: {
}
})
5.2 创建页面及css样式
该样式比较讲究,将刊号撑大。右边是月数汉字在上,年份在下。
(1)No、刊号index、分割符 划为一个view内(index-container);发行时间整体在一个view内(date-container)。
// index.wxml
<view class="container">
<view class="index-container">
<text class="plain">No.</text>
<text class="index">{{_index}}</text>
<view class="line"></view>
</view>
<view class="date-container">
<text class="month">{{month}}</text>
<text class="year">{{year}}</text>
</view>
</view>
(2)大容器是flex布局,并且是横向的,高度与index的高度相同。期刊布局中的三个元素靠下对齐使用baseline。分割符直接使用view的border-left: 1px solid black;使其成为一条线。
.container {
/* 根据08的高度 */
height: 60rpx;
display: inline-flex;
flex-direction: row;
}
.index-container{
display: flex;
flex-direction: row;
align-items: baseline;
}
.plain{
font-size: 32rpx;
}
.index {
font-size: 60rpx;
/* 限制行高 */
line-height: 60rpx;
font-weight: 800;
margin-right: 14rpx;
}
.line {
height: 44rpx;
margin-right: 14rpx;
border-left: 1px solid black;
}
(3)日期布局date,flex垂直布局,设置下日期大小即可。
.date-container {
display: flex;
flex-direction: column;
margin-top: 5rpx;
}
.month {
font-size: 24rpx;
line-height: 24rpx;
}
.year {
font-size: 20rpx;
}
(4)最后是classic页面布局。episode组件和like组件同属于一个布局header。
/* header */
.header {
display: flex;
flex-direction: row;
height: 100rpx;
align-items: center;
// 四周空一点
justify-content: space-between;
// 上下有分割线
border-top: 1px solid #f5f5f5;
border-bottom: 1px solid #f5f5f5;
}
.episode {
margin-left: 20rpx;
margin-top: 4rpx;
}
.like {
margin-top: 6rpx;
}
5.3 在展示页面引入
// classic.json 中引入组件
{
"usingComponents": {
"v-like":"/components/like/index",
"v-movie": "/components/classic/movie/index",
"v-episode": "/components/episode/index"
}
}
// classic.wxml中显示组件
<v-episode index='{{classic.index}}' />
6、开发导航组件navi
分析:
该组件比较简单,由两个image、text组成,整体在一个view中。navi组件中的标题和刊号是需要外部传入的数据。
6.1 导航组件的属性和数据
- 设置组件中的属性,这里有title、first、latest属性;
- 设置组件中变量,这里有图片的路径,disLeftSrc、leftSrc、disRightSrc、rightSrc。
navi.js文件中
// navi.js
/**
* 组件的属性列表
* first:是否最早期刊
* latest: 是否最新期刊
*/
properties: {
title: String,
first: Boolean,
latest: Boolean
},
/**
* 组件的初始数据
*/
data: {
disLeftSrc: 'images/triangle.dis@left.png',
leftSrc: 'images/triangle@left.png',
disRightSrc: 'images/triangle.dis@right.png',
rightSrc: 'images/triangle@right.png'
},
6.2 导航组件的布局和样式
navi.wxml中
// navi.wxml组件静态页面
<view class="container">
<image class="icon" src="{{latest?disLeftSrc:leftSrc}}"></image>
<text class="title" />{{title}}</text>
<image class="icon" src="{{first?disRightSrc:rightSrc}}"/></image>
</view>
// navi.wxss组件样式,首先整体处在flex布局,按行排列,两边对齐。
// 这里布局必须给个宽度,不然会导致组件不能占满屏幕。
.container {
width: 600rpx;
height: 80 rpx;
display:flex;
flex-direction: row;
justify-content: space-between; // 两边对齐
align-items: center;
background-color: #f7f7f7;
border-radius: 2px;
}
.icon {
height: 80rpx;
width: 80rpx;
}
.title {
font-size: 28rpx;
}
6.3 导航组件的应用
在classic.json中注册
{
"usingComponents": {
"v-navi": "/components/navi/navi"
}
}
对导航组件在classic中进行排版,让其为绝对布局距离底部40rpx。
// classic.wxml 使用navi组件,传入自定义title,是否起始页、最新页参数。
<v-navi class="navi" title="123" first="{{first}}" latest="{{latest}}"/>
// classic.wxss
.navi {
// 绝对布局,距离底部40rpx
position: absolute;
bottom: 40rpx;
}
// classic.js
data : {
latest: true, // 最新期刊,默认是最新期刊
first: false // 最早期刊。默认不是最早
}
6.4 导航组件的左右切换func
- 给navi组件编写自定义函数onLeft、onRight,点击图片给页面传递自定义事件名称;
- 给页面绑定监听事件并做切换处理。
PS:自定义事件的传递
navi组件中
// navi.wxml
<view class="container">
<image bind:tap="onLeft" class="icon" src="{{first?disLeftSrc:leftSrc}}"></image>
<text class="title">{{title}}</text>
<image bind:tap="onRight" class="icon" src="{{latest?disRightSrc:rightSrc}}"></image>
</view>
// navi.js
methods: {
onLeft: function(event){
console.log("left");
// 如果不是最早的期刊,点击了向左的按钮会响应。
if(!this.properties.first){
this.triggerEvent('left',{} , {});
}
},
onRight: function(event){
console.log("right");
// 如果不是最早的期刊,点击了向左的按钮会响应。
if(!this.properties.latest){
this.triggerEvent('right',{} , {});
}
}
}
页面的监听和声明处理函数
// classic.wxml
<v-navi bind:left="onPrevious" bind:right="onNext" class="navi" title="《饮食男女》" first="{{first}}" latest="{{latest}}"></v-navi>
// classic.js
// 自定义的onLeft-onPrevious和onRight-onNext函数
onPrevious: function(e){
var behavior = e.detail.behavior;
},
onNext: function(e){
var behavior = e.detail.behavior;
},
7、music、essay组件开发并完善(behavior)
7.1 music组件
music组件主要由播放、暂停图片和音乐背景图,还有一句内容词组成。
由外部传入的只有img和content。
// music.wxml
<view>
<image src="{{img}}"></image>
<image src="{{playSrc}}"></image>
<image src="image/music@tag.png"></image>
<text>{{content}}</text>
</view>
// music.js
properties: {
img: {
type: String
},
content:String
},
/**
* 组件的初始数据
*/
data: {
pauseSrc: 'images/player@pause.png',
playSrc: 'images/player@play.png'
},
7.2 essay组件(文章组件)
essay组件主要由句子背景图,还有一句内容词组成。
由外部传入的只有img和content。
所以可以使用behavior对相同代码抽取出来,进行公用。
(1)定义behavior对象classic-beh.js,抽取公共代码
// behavior 定义组件中共有的属性 主要作用是抽离出相同的代码
// Behavior 与index.js中的Component是类似的
let classicBeh = Behavior({
properties: {
img: {
type: String
},
content:String
},
/**
* 组件的初始数据
*/
data: {
},
})
export {classicBeh}
(2)组件中的使用behavior 同时将music组件以及movie组件中替换
替换essay组件中的properties。
// components/classic/essay/essay.js
import {classicBeh} from '../classic-beh.js'
Component({
/**
* 组件的属性列表
*/
behaviors:[classicBeh],
properties: {
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
}
})
8、完善并优化-导航栏组件的切换
分析:在classic页面中,我们给向前和向后按钮设置了初始状态。在点击过程中index期刊号会随之变化,内容也会变化,因此我们需要将页面的数据进行动态变化,也就是setData进行渲染。
8.1 将判断latest、first期刊号方法,封装进model的classicModel中
// 判断当前期刊是否首期
isFirst(index){
return index == 1 ? true:false;
}
// 判断当前期刊是否最新
isLatest(index){
let latestIndex = this._getLatestIndex();
return latestIndex == index ? true:false;
}
// 缓存最新index期刊号
_setLatestIndex(index) {
wx.setStorageSync('latest', index);
}
_getLatestIndex(){
let index = wx.getStorageSync('latest');
return index;
}
8.2 将后台获取上一期、下一期内容方法,同样封装进classicModel中
需要以【当前index期刊号,回调函数传结果】为参数,向后台发送请求。
// 获取当前上一期数据的函数
getPrevious(index, sCallBack){
this.request({
url: 'classic/'+ index + '/previous',
success: (res) =>{
sCallBack(res)
}
})
}
// 获取当前下一期数据的函数
getNext(index, sCallBack){
this.request({
url: 'classic/'+ index + '/next',
success: (res) =>{
sCallBack(res)
}
})
}
8.3 在页面classic.js中使用classicModel定义方法
// 自定义的onLeft-onPrevious和onRight-onNext函数
onPrevious: function(e){
// 当前index号
let index = this.data.classic.index;
classicModel.getPrevious(index, (res)=>{
// 异步返回上一期数据
// 渲染新数据
this.setData({
classic: res,
latest: classicModel.isLatest(res.index),
first: classicModel.isFirst(res.index)
})
})
},
onNext: function(e){
let index = this.data.classic.index;
classicModel.getNext(index, (res)=>{
this.setData({
classic: res,
latest: classicModel.isLatest(res.index),
first: classicModel.isFirst(res.index)
})
})
},
9、onNext和onPrevious方法重构
onNext方法和onPrevious方法的逻辑基本相同,所以将两个方法进行合并。将不同的地方使用参数进行传递。
9.1 classicModel中的getNext()和getPrevious()整合成getClassic(x,x,x)方法
/***代码重构***/
// 获取期刊数据
getClassic(index, nextOrPrevious, sCallBack){
this.request({
url: 'classic/' + index + "/" + nextOrPrevious,
success: (res)=>{
sCallBack(res);
}
})
}
9.2 页面classic.js中的getNext()和getPrevious()整合成私有方法_updateClassic(…)
// 重构getNext()、getLatest()代码
_updateClassic: function(nextOrPrevious){
let index = this.data.classic.index;
classicModel.getClassic(index, nextOrPrevious, (res)=>{
this.setData({
classic: res,
latest: classicModel.isLatest(res.index),
first: classicModel.isFirst(res.index)
})
})
},
// 自定义的onLeft-onPrevious和onRight-onNext函数
onPrevious: function(e){
_updateClassic("previous");
},
onNext: function(e){
_updateClassic("next");
},
10、getClassic()方法使用本地缓存(提高select效率各方面)
/***代码重构***/
// 获取期刊数据
getClassic(index, nextOrPrevious, sCallBack){
// 缓存中寻找 or API
// key 确定key
let key = nextOrPrevious == "next" ?
this._getKey(index + 1):this._getKey(index - 1)
let classic = wx.getStorageSync(key);
if(!classic){
this.request({
url: 'classic/' + index + "/" + nextOrPrevious,
success: (res)=>{
wx.setStorageSync(this._getKey(res.index), res);
// 返回res
sCallBack(res);
}
})
}else {
sCallBack(classic);
}
}
// 自定义key名称区分于index即可
_getKey(index) {
return "classic-" + index;
}