背景
之前 table 数据和总页数是通过一个请求完成,现在为了优化加载速度,决定对 table 数据和总页数分开请求。做完之后发现一个问题。
不符合预期的现象
element-ui 分页组件,点击第二页, table 数据加载完成后,页码高亮从第二页变为第一页;点第三页,现象一样。
这是最直接的现象。
我们的预期是
数据加载完后,高亮的依然是之前点击的页码;
实际是:高亮的页码变到了 1。
开始 Debug
我们的目标:找到 【高亮的页码变到了 1】的根源在哪,然后修正根源,进而修正现象。
- 找到控制页码高亮的变量
通过 Vue dev tool 可以很容易审查到:ElPagination -> data -> internalCurrentPage。
果然 internalCurrentPage === 1。
在点击页码 2 时看到 props 里 currentPage 也跟着变成了 2,但在 internalCurrentPage 变成 1 时,并没有变成 1。
currentPage 是 props,数据由外面决定,由此可得,在页码数据变化的过程中, props -> currentPage 这个节点上还是正确的。internalCurrentPage 这里不正确了。
所以问题锁定在 currentPage => internalCurrentPage 的计算过程。
- currentPage => internalCurrentPage 计算过程
通过查看 ElPagination 的源码,在组件内搜索关键字 internalCurrentPage,发现有20多处,设置 internalCurrentPage 发生在
1. handleCurrentChange
2. prev
3. next
一句话,就是点击导致页码变化时。
而且这些函数在设置 internalCurrentPage 值都是通过 getValidCurrentPage 计算来的。
所以问题锁定在 getValidCurrentPage 的计算过程。
- 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)一样。这显然有问题。
根本原因,是用静止的观点思考应用,现在的应用基本都是背景相关的,运行的过程可以想象人的活动,时间不能倒流,即使一样的动作在过去和现在产生的效果也绝不一样。