Weapp影视评分项目开发(12):使用 mixins 实现代码复用

639 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第12天,点击查看活动详情

前言

本篇内容较为深入,使用微信小程序提供的 behaviors (即mixins)实现上拉加载与下拉刷新功能的代码复用。

知识点

behaviors 的实现 上拉加载与下拉刷新代码的封装

一、Behavior 的介绍

微信小程序提供了 behaviors 实现组件间代码共享,因为与 Vuemixins 类似,所以我还是习惯的称它为 mixins

上篇文章我们实现了下拉刷新与上拉加载更多的功能,这两个功能将会贯穿整个项目,在非常多的页面中使用到,所以我们需要将这部分代码封装为 mixins,来提高开发效率,降低维护成本。

本文不会简单的罗列官方文档中 behaviors 的使用方法,而是对官方文档进行总结,并聊聊几个官方文档未说明或者较为模糊的点。

  • 因为 behaviors 是组件能力,非页面级,所以它支持的属性、方法和生命周期,与组件基本一致;
  • 如果我们定义组件的 behaviors,通常会使用到 properties
  • behaviors 内支持再注入 behaviors
  • Page 页面也可以注入 behaviors,把它当成组件来使用,Page 可以调用 behaviorsmethods 中定义的方法;
  • 和组件一样,behaviors 的生命周期优先于 Page

behaviors 中没有 Page 中页面级如 onPullDownRefresh onPageScroll 等方法,所以这部分功能无法使用 behaviors 实现复用。

二、 使用 behaviors 实现下拉刷新与上拉加载函数

我们在项目根目录下新建 mixins 文件夹用于存放复用的代码,创建一个 getInfiniteData.js 文件存放下拉刷新与上拉加载函数的 behaviors,结构如下:

├── mixins
│   └── getInfiniteData.js  // 下拉刷新与上拉加载功能 

下拉刷新与上拉加载函数由三部分组成:

  • data:默认的数据
  • resetData:重置 data 数据的方法
  • getData:获取数据的接口方法

loadMore 方法是页面中用于调用 getData 方法的函数,它会将接口与查询条件传递给 getData 方法

1. data 初始化数据介绍

data 中初始化设置了一些默认字段,这些字段基本涵盖了列表页面中需要的字段。虽然这样限定了各个列表页面的字段名称,但是如果页面有特殊需求,我们可以在 Pagedata 中重写覆盖或者添加新的字段来实现个性化需求。默认字段说明如下:

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. 重置 dataresetData 函数

resetData 函数作用是重置 data 中的数据,当用户执行下拉刷新动作或切换查询条件时,我们需要将数据恢复到默认状态(主要是 page 置为 1list 数据清空操作),代码如下:

// 重置列表需求的 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 最好集中设置;
  • 接口请求函数未使用 try catch,是因为之前在 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.exportscommonJs 规范。

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 在日常业务中实现代码的复用,还是非常有效的,下一篇,我将会对上面的代码再进行精简,敬请期待。