Weapp影视评分项目开发(15):搜索页的实现(中)防抖与 wx.request 取消请求

896 阅读4分钟

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

前言

本篇是搜索页实现的中篇,我们将使用防抖函数、网络请求取消实现模糊搜索与搜索结果的关键字标红展示功能。

知识点

函数防抖的使用 网络请求取消 搜索结果关键字标红

一、防抖函数

需求场景说明:我们希望用户在输入搜索关键字时,能立即触发接口查询,减少用户操作。但是输入框的 input 事件会在用户输入时持续触发,所以我们需要限制该事件的频率,比如用户在输入完上一个字后 100ms 内未再次输入,我们就认为用户输入结束,发起网络请求。防抖函数的作用,就是实现延时请求与频率限制。

1. 防抖函数的实现

防抖函数的实现有很多的教程,再次不做过多赘述。

/**
 * @desc 防抖函数
 * @param {function} func 目标函数
 * @param {number} [wait=100] 延迟执行毫秒数
 */
export const debounce = (func, wait = 100) => {
  let timeout;
  return function (event) {
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      func.call(this, event);
    }, wait);
  };
}

2. 防抖函数的挂载

我们将防抖函数定义在 utils/index.js 文件中,并在 app.js 中将其作为参数传递给 App

import { debounce } from "./utils/index"; // 防抖

App({
  debounce
})

3. 防抖函数的使用

因为我们将搜索框与搜索结果模块拆分成了组件,所以我们需要通过他们的父组件来实现消息传递。
现在有个问题:防抖函数的处理是放在搜索组件还是搜索查询的组件中?
我个人觉得放在搜索查询页面(search-result 组件)更好,因为我们最终想要控制的是接口查询的频率,放在这里控制职责更清晰些。
所以我们在 search-result 组件中发起网络请求的方法前做了如下处理:

methods: {
  // 查询方法,注意此处的写法
  doSearch: app.debounce(function () {
    this.search();
  }),
  // 搜索关键字查询
  async search() {
    // 接口调用 略
  }
}

二、取消请求

1. 为什么需要取消请求

原因:虽然有防抖函数降低了请求频率,但是它无法避免会有多次请求发起。举个例子,用户想输入“钢铁侠”三个字搜索影视,当用户输入“钢铁”后停顿了下,此时接口发起请求,我们记为 request_1 ,在请求过程中用户继续输入“侠”字,接口又发起一个请求,记为 request_2,当 request_1 如果耗时更久还未返回时,request_2却先返回了,之后 request_1 才返回,这时搜索结果将会展示 request_1 的内容,造成查询关键字与显示结果不匹配。
解决方案:接口有新请求时取消上次的请求。

取消请求是客户端层面的事件,后端接口还是会接收到接口请求并返回,只是客户端不接收。

2. 取消请求的实现

我们利用小程序 wx.requestabort() 方法来中断请求,主要代码如下:

Component({
  data: {
    keyword: "",   // 查询关键字
    page: 1,       // 当前页码
    movies: [],    // 查询结果列表
    _cancel: null, // 网络请求任务对象
  },

  methods: {
    // 搜索接口
    async search() {
      // 如果当前已存在请求任务,则取消该任务
      if (this.data._cancel) {
        this.data._cancel.abort();
        this.data._cancel = null;
      }

      const params = {
        page: this.data.page,
        keyword: this.data.keyword
      }
      
      // 将请求任务对象赋值给 _cancel 字段
      this.data._cancel = wx.request({
        url: HOST + '/search',
        data: params,
        header: {
          'content-type': 'application/json',
          Authorization: `Bearer ${app.token}`
        },
        success: ({ code, data, total }) => {
          if (code === 200) {
            this.setData({
              movies: this.data.movies.concat(data),
              page: ++this.data.page,
            })
          }
        },
        fail: (err) => {
          console.error(err);
        },
        complete: () => {
          // 请求完成后将请求人物对象置空
          this.data._cancel = null;
        }
      })
    }
  }
})

我们之前接口在发起请求时,一般会设置个 loading 状态标志来阻止接口请求过程中被再次触发,在这里是不能做这个限制的,多次的请求会被中断掉。

三、关键字标红

1. 效果展示

如下图所示,我们搜索关键字“钢铁侠”时,返回的影视列表的影视名称中会把关键字“钢铁侠”标红。

image.png

2. 标红实现

1) web 中实现

如果是在 web 中,我们只需要使用字符串替换函数,将关键字使用标签进行包裹,然后给标签设置样式即可。vue 代码实现如下:

computed: {
  title() {
    reurn this.movie.title.replace(this.keyword, `<span class="keyword">${this.keyword}</span>`);
  }
}
<div v-html="title"></div>

之后我们给 .keyword 增加样式即可。

2) 小程序中实现(使用 rich-text 标签)

微信从基础库 1.4.0 开始支持了 rich-text 标签,它可以解析受信任的 html 标签,所以在逻辑部分我们可以按照 web 的方式处理,模板部分代码如下:

<rich-text nodes="{{ title }}" />
3) 小程序中实现(拆分数组遍历)

之前在没有 rich-text 标签时,该怎么实现呢?
我们可以将标题按照关键字进行拆分为数组,如“钢铁侠3”拆分为 ['钢铁侠', '3'],之后再使用 wx:for 遍历该数组,如果数组元素与关键字一致,则给其赋予不同的样式,代码如下:

computed: {
  title(data) {
    return data.movie.title.replace(new RegExp(`${data.keyword}`, 'g'), `%%${data.keyword}%%`).split('%%')
  }
}

模板中遍历该数组,如果元素与关键字一致,则赋予 .keyword 样式:

<block wx:for="{{ title }}">
  <text class="{{ keyword === item ? 'keyword' : '' }}">{{ item }}</text>
</block>

总结

以上即为搜索页实现的中篇,主要讲述了防抖函数的使用,取消请求的实现,以及搜索结果关键字标红的处理。
下一篇,我们将完成搜索页的剩余部分:tab 页切换与历史搜索记录的实现。