记一次 Debug

534 阅读3分钟

背景

之前 table 数据和总页数是通过一个请求完成,现在为了优化加载速度,决定对 table 数据和总页数分开请求。做完之后发现一个问题。

不符合预期的现象

element-ui 分页组件,点击第二页, table 数据加载完成后,页码高亮从第二页变为第一页;点第三页,现象一样。

这是最直接的现象。

我们的预期是

数据加载完后,高亮的依然是之前点击的页码;
实际是:高亮的页码变到了 1。

开始 Debug

我们的目标:找到 【高亮的页码变到了 1】的根源在哪,然后修正根源,进而修正现象。

  1. 找到控制页码高亮的变量

image.png

通过 Vue dev tool 可以很容易审查到:ElPagination -> data -> internalCurrentPage。

果然 internalCurrentPage === 1。

在点击页码 2 时看到 props 里 currentPage 也跟着变成了 2,但在 internalCurrentPage 变成 1 时,并没有变成 1。

debug.gif

currentPage 是 props,数据由外面决定,由此可得,在页码数据变化的过程中, props -> currentPage 这个节点上还是正确的。internalCurrentPage 这里不正确了。

所以问题锁定在 currentPage => internalCurrentPage 的计算过程。

  1. currentPage => internalCurrentPage 计算过程

通过查看 ElPagination 的源码,在组件内搜索关键字 internalCurrentPage,发现有20多处,设置 internalCurrentPage 发生在
1. handleCurrentChange
2. prev
3. next
一句话,就是点击导致页码变化时。
而且这些函数在设置 internalCurrentPage 值都是通过 getValidCurrentPage 计算来的。
所以问题锁定在 getValidCurrentPage 的计算过程。

  1. getValidCurrentPage 的计算过程
function getValidCurrentPage(value) {
  value = parseInt(value, 10);

  var havePageCount = typeof this.internalPageCount === 'number';

  var resetValue = void 0;
  if (!havePageCount) {
    if (isNaN(value) || value < 1) resetValue = 1;
  } else {
    if (value < 1) {
      resetValue = 1;
    } else if (value > this.internalPageCount) {
      resetValue = this.internalPageCount;
    }
  }

  if (resetValue === undefined && isNaN(value)) {
    resetValue = 1;
  } else if (resetValue === 0) {
    resetValue = 1;
  }

  return resetValue === undefined ? value : resetValue;
}

很多地方在试图把值设置为 1,但是我们不知道那个分支发挥的作用:
一个直觉的办法,我们把其中一个改为8,然后看页码最终变为 8了吗,以此锁定有效分支。当然打 log 或断点都是可以的。

最终我们锁定有效分支:

} else if (value > this.internalPageCount) {
      resetValue = this.internalPageCount;
    }

 } else if (resetValue === 0) {
    resetValue = 1;
  }

所以根本原因是 this.internalPageCount 在这一刻 === 0。

问题变成 internalPageCount 如何来的:

通过以 internalPageCount 为关键字搜索源码,不难看到,

computed: {
    internalPageCount: function internalPageCount() {
      if (typeof this.total === 'number') {
        return Math.ceil(this.total / this.internalPageSize);
      } else if (typeof this.pageCount === 'number') {
        return this.pageCount;
      }
      return null;
    }
  },

internalPageCount 是计算属性,所以它是 0 的原因是:

Math.ceil(this.total / this.internalPageSize);

至此我们把数据的源头锁定到应用内 total, 我们传入的总条数 total 有问题。

回顾下

为什么一开始没有意识到 total 有问题,毕竟这次需求的变动就是关于 total 懒加载的 —— 第一次会返回一个假的 total( -1),随后通过另一个接口更新这个错误的 total。以我直觉的理解,即使中间有一段时间 total 是错的 -1,也没关系。最终 total 正确了,计算结果会正常。

其实这个直觉有个重大缺陷:total 为 -1 时,我们把此时应用的状态记为 S1,由这个值计算而产生一些副作用(在这里是表现在页面上的:高亮页码被置为了 1),此时 app 状态记为 S2;
当 total 值得到纠正时,应用的状态是 S2,而我们却期待应用的表现依然和 (total, S1)一样。这显然有问题。

根本原因,是用静止的观点思考应用,现在的应用基本都是背景相关的,运行的过程可以想象人的活动,时间不能倒流,即使一样的动作在过去和现在产生的效果也绝不一样。