开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第12天,点击查看活动详情
前言
本篇内容较为深入,使用微信小程序提供的 behaviors (即mixins)实现上拉加载与下拉刷新功能的代码复用。
知识点
behaviors 的实现 上拉加载与下拉刷新代码的封装
一、Behavior 的介绍
微信小程序提供了 behaviors 实现组件间代码共享,因为与 Vue 的 mixins 类似,所以我还是习惯的称它为 mixins。
上篇文章我们实现了下拉刷新与上拉加载更多的功能,这两个功能将会贯穿整个项目,在非常多的页面中使用到,所以我们需要将这部分代码封装为 mixins,来提高开发效率,降低维护成本。
本文不会简单的罗列官方文档中 behaviors 的使用方法,而是对官方文档进行总结,并聊聊几个官方文档未说明或者较为模糊的点。
- 因为
behaviors是组件能力,非页面级,所以它支持的属性、方法和生命周期,与组件基本一致; - 如果我们定义组件的
behaviors,通常会使用到properties; behaviors内支持再注入behaviors;Page页面也可以注入behaviors,把它当成组件来使用,Page可以调用behaviors的methods中定义的方法;- 和组件一样,
behaviors的生命周期优先于Page;
behaviors中没有Page中页面级如onPullDownRefreshonPageScroll等方法,所以这部分功能无法使用behaviors实现复用。
二、 使用 behaviors 实现下拉刷新与上拉加载函数
我们在项目根目录下新建 mixins 文件夹用于存放复用的代码,创建一个 getInfiniteData.js 文件存放下拉刷新与上拉加载函数的 behaviors,结构如下:
├── mixins
│ └── getInfiniteData.js // 下拉刷新与上拉加载功能
下拉刷新与上拉加载函数由三部分组成:
data:默认的数据resetData:重置data数据的方法getData:获取数据的接口方法
loadMore方法是页面中用于调用getData方法的函数,它会将接口与查询条件传递给getData方法
1. data 初始化数据介绍
data 中初始化设置了一些默认字段,这些字段基本涵盖了列表页面中需要的字段。虽然这样限定了各个列表页面的字段名称,但是如果页面有特殊需求,我们可以在 Page 的 data 中重写覆盖或者添加新的字段来实现个性化需求。默认字段说明如下:
data: {
id: null, // 资源ID,可选值
loading: false, // 加载状态标志位
noData: false, // 无数据标志位,后端返回结果为 0 时,置为 true,上拉加载不再执行
noMoreData: false, // 无更多数据标志位,当返回数据条数小于 per_page 条数时置为 true,拉加载不再执行
form: {}, // 查询条件
list: [], // 结果列表
total: 0, // 结果总数
page: 1, // 当前页码
per_page: 20, // 每页条数
isError: false, // 接口异常标志位
}
2. 重置 data 的 resetData 函数
resetData 函数作用是重置 data 中的数据,当用户执行下拉刷新动作或切换查询条件时,我们需要将数据恢复到默认状态(主要是 page 置为 1,list 数据清空操作),代码如下:
// 重置列表需求的 data 数据,并发起列表数据请求
resetData() {
if (this.data.loading) return; // 如果接口已经在请求中,则不重置数据,防止用户持续下拉刷新
this.setData({
loading: false,
page: 1, // 当前页码置为 1
list: [], // 结果列表清空
total: 0, // 结果总数置为 0
noData: false, // 无数据状态置为 false
noMoreData: false,// 无更多数据置为 false
isError: false, // 接口异常置为 false
})
this.loadMore(); // 调用页面中的接口请求方法,后面会介绍
},
3. 实现接口请求方法
/**
* @desc 加载分页数据方法
* @param {function} fn 接口请求方法
* @param {any} args 接口请求参数
* @return
*/
async getData(fn, ...args) {
if (this.data._isError) return; // 接口异常后不再执行请求
// 无更多数据状态判断
if (this.data.noMoreData && this.data.page === 1 && this.data.list.length === 0) {
this.setData({
noMoreData: false
})
}
// 加载中或者无更多数据时,不再发起接口请求
if (this.data.loading || this.data.noMoreData) return;
// 加载状态,用于页面中控制加载动画的显示
this.setData({ loading: true })
// 接口查询参数
const params = {
page: this.data.page,
per_page: this.data.per_page,
...this.data.form
};
// 接口请求
const { code, data, total } = await fn(...args, params);
// 如果是用户下拉刷新,则关闭下拉动画(非用户下拉调用此方法无变化)
wx.stopPullDownRefresh();
// 请求成功,业务状态码统一为 200
if (code === 200) {
let noData = false; // 默认数据状态为有数据
// 当前页为第1页且返回数据为空时,则说明无数据
// 不使用 total 字段值判断,是因为有的接口不返回此字段
if (this.data.page === 1 && data.length === 0) {
noData = true;
} else {
noData = false;
}
this.data.isError = false; // 此处未使用 setData 方法,因为此字段不需要在页面模板中使用
// 当前返回数据条数少于请求的每页条数,则说明无更多数据
if (data.length < this.data.per_page) {
this.setData({
noMoreData: true
})
}
// 将返回数据添加到 list 中
this.data.list.push(...data)
// 统一设置数据与状态值等
this.setData({
noData,
list: this.data.list,
page: ++this.data.page,
total: total || 0,
})
} else {
// 返回异常
this.data.isError = true;
}
// 页面渲染完成后关闭加载动画字段
wx.nextTick(() => {
this.setData({
loading: false
})
});
}
以上函数中有几处注意点:
- 未在
wxml模板中到的字段,可以不实用setData方法赋值,直接改变改值是有效的; - 返回数组的追加方法有多种,常用方法如下:
const list = this.data.list.concat(data); // ES5 数组合并 const list = [...this.data.list, ...data]; // ES6 的数组展开 this.data.list.push(...data); // 注意 push 方法会改变原数组,返回值为数组长度 - 一些如状态开关判断,防止接口重复请求;
- 多个字段的
setData最好集中设置; - 接口请求函数未使用
trycatch,是因为之前在http的封装中做了统一异常处理,异常情况也会有返回值,不会中断请求。 await fn(...args, params)函数中,...args是接口中定义的参数,可能有多个,所以用展开运算符接收,如获取影视的幕后知识列表接口,...args对应的就是id:// 接口定义 export const getMovieKnowledges = (id, params) => http.get(`/movies/${id}/knowledges`, params);// 接口使用 import { getMovieKnowledges } from "./api/movie"; // 导入上方定义的接口 this.getData(getMovieKnowledges, this.data.id); // 调用 mixins 中的 getData 方法
4. behaviors 导出
我们按照以下格式将 behaviors 导出, module.exports 是 commonJs 规范。
module.exports = Behavior({
data: {},
// 组件实例创建完成后调用页面中的方法去触发接口请求
// 名称约定,Page 中就无需调用
attached() {
this.loadMore();
},
methods: {
// 重置函数
resetData() {},
// 获取数据
async getData(fn, ...args) {}
}
})
三、behaviors 的使用
1. 将 behaviors 挂载到全局
我们在 app.js 中,引入该混入并挂载到 wx 上,这样就避免了每次需要在使用页面导入该文件的步骤,且避免了小程序的 js 只能使用相对路径问题:
// app.js
import getInfiniteData from "./mixins/getInfiniteData";
wx.getInfiniteData = getInfiniteData;
2. “正在热映”页面的实现
经过以上封装后,我们使用起来就非常简单了,以正在“正在热映”页面为例:
- 开启下拉刷新:
// pages/movies/theater/index.json
{
"enablePullDownRefresh": true // 开启下拉刷新
}
- 实现接口调用
// pages/movies/theater/index.js
import { getMovieTheater } from "../../../api/api"; // 导入热映影视接口
Page({
behaviors: [wx.getInfiniteData], // 注入混入
// 该方法会在 mixins 中的 attached 生命周期中被调用
loadMore() {
this.getData(getMovieTheater); // 调用正在热映方法
},
// 用户下拉刷新
onPullDownRefresh() {
this.resetData();
},
// 用户上拉触底
onReachBottom() {
this.loadMore();
}
})
- 模板页面
<!-- pages/movies/theater/index.wxml -->
<view class="main">
<movie-item wx:for="{{ list }}" movie="{{ item }}" wx:key="id" />
</view>
以上这些代码,就实现了“正在热映”的页面,是不是非常简洁。
总结
微信小程序的 behaviors 在日常业务中实现代码的复用,还是非常有效的,下一篇,我将会对上面的代码再进行精简,敬请期待。