el-pagination 修改每页数量触发两次请求/页面空白的分析

element-ui 版本:2.13.2

对于页码(current-page)和每页条数(page-size)的改变,el-pagination 分别会触发 size-change 和 current-change 事件。为了方便同步数据,current-page 和 page-size 都使用了 sync 修饰符。

这就导致了一个问题:如果修改每页条数使得当前页码超出范围,el-pagination 会在触发 size-change 事件后再触发 current-change 事件。如果没有任何防抖节流措施,会看到网络面板里有两个请求,并且第一个请求的参数页码可能是不对的。这里说可能,是因为对于参数的处理会导致结果不同。

首先,对于请求,我一般会用一个变量标记是否正在进行,如果是,禁止再次请求。

getData() {
  if (this.loading) return
  this.loading = true
  request({})
  // …………
}

这样处理后就避免了两次请求的情况。但是,这也导致了页面空白的问题。

解构导致绑定值无法及时更新

vue 是双向绑定的,sync 修饰符可以让子组件改变父组件的值。当改变每页条数导致页码改变的时候,只有 current-change 触发的时候页码才会是正确的值。但我代码里只会发出 size-change 的请求。如果请求时我对查询对象进行解构处理,即实际传给接口的值脱离了 vue 的响应式系统,就会导致页面空白问题。为此,可以将解构、请求等处理放到 nextTick 里,确保 current-page 变化之后再获取数据。

// 获取列表
fetchData() {
  if (this.listLoading) return

  this.listLoading = true

  this.$nextTick(() => {
    const tempQuery = { ...this.query }
    // 一些处理

    getInvoiceRecordList(tempQuery)
      .then(response => {
        this.list = response.data ?? []
        this.total = response.page?.totalItems ?? 0
      })
      .finally(() => {
        this.listLoading = false
      })
  })
},

虽然这解决了问题,但还有一个疑问:为什么没有解构的参数就可以不使用 nextTick 而获取到正确的值呢?

Axios 的请求不是立即发出的

在不对 this.query 解构的情况下,虽然第一次执行 fetchData 时请求参数不正确,但是最终还是拿到了正确的数据,实际参数也是正确的。这说明在 axios 发出请求之前,current-change 就已经完成了页码的修改。难道 axios 的请求不是立即发出的?来,看下 axios 的代码:

// axios/lib/core/Axios.js
/**
 * Dispatch a request
 *
 * @param {Object} config The config specific for this request (merged with this.defaults)
 */
Axios.prototype.request = function request(config) {
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof config === 'string') {
    config = utils.merge({
      url: arguments[0]
    }, arguments[1]);
  }

  config = utils.merge(defaults, {method: 'get'}, this.defaults, config);
  config.method = config.method.toLowerCase();

  // Hook up interceptors middleware
  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);

  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

axios 把拦截器配置和请求放到了 promise.then 里执行。而 promise.then 是微任务。当真正发起请求的时候,vue 已经把 current-page 同步为正确的值了。