前言
事情源于一次跟CTO的交流,当时由于紧张而且距离处理这些 bug 的时间过得较长,所以当时答的一塌糊涂,所以就想着重新梳理下当时回答的问题,讲清楚问题的原由,做到功夫在平时,同时加深记忆。
总共说了两件事儿:
1. 翻页问题的异常。
2. SSR 与 CSR 界限的区分。
总的原因
发生这些问题有一个大的前提是因为,项目中了用到了服务端渲染(SSR),那么就需要去区分 SSR 渲染与 CSR 渲染的情况。也因为之前没有处理过 SSR 的项目,所以在前期一些组件跟业务处理思想还是 CSR 的模式,导致了一系列的问题。
分页问题的异常
BUG 描述
项目已经上线了很长一段时间了,突然有用户反馈,浏览器回退按钮回退页面的时候路由已经回退成功,但是页面数据还是保持的当前的数据。
BUG 分析
分页组件时统一封装的,那就去看看咋回事儿吧!先看代码
const clickPage = (page) => { if (callBack && typeof callBack === 'function') { // 这个方法是去请求分页的数据 callBack(page); } if (pathName.includes('?')) { history.push(`${pathName}&page=${page}`); } else { history.push(`${pathName}?page=${page}`); } }; <Pagination.Item href={href} onClick={(e) => { e.preventDefault(); clickPage(); }} > 页码 </Pagination.Item>
上面给出一个页码的例子,
Pagination.Item
会被渲染成a
标签, 所以从这里就能确定产生这个问题的原因了,我们先去拿了当前点击页面的数据,然后再去跳转了页面。
当时看到这就懵逼了,怎么会有这么奇葩的操作呢?分页不是应该先跳转页面,然后在页面内去监听页面的变化,在去请求数据这个流程么?为啥子要这样处理呢?
这就要回到前面所说的大原因了,SSR 跟 CSR 加载场景需要区分,为什么要去区分?因为如果所有的模式都去走 SSR 的话,服务端的压力会很大,所以一些用户的点击操作比如翻页、排序等,这些操作就可以采用 CSR 的模式,所以当时在写一些公共组件(分页,排序)时,就采用了这种写法:
- 分页上面保留地址,有利于搜索引擎的收录跟 SEO,然后实际上采用客户端跳转方式去改变路由在跳转前去拿数据。
- 这样能保证肯定是 CSR 的方式渲染,不用去区分 SSR 的影响。
所以基于上面两种原因,采用了这种方式去实现,其实这套逻辑如果不考虑浏览器回退(低频操作)的方式,是完全能够使用的,这也是一开始为什么没有发现这个bug的原因。
BUG 解决
既然 BUG 是由上面那两种‘优点’造成的,那就对症下药吧。
- 需要去监听路由的变化再去根据对应得参数获取数据。
- 保证 SSR 跟 CSR 互不影响,不然服务端渲染过了客户端在执行一遍,太浪费资源了,加载效果也会受影响。
按照这个思路去修改,首先去掉所有传入的回调函数,让组件只是完成路由的跳转,这样整理下来居然发现了一个好处,避免了回调函数的层层传入,竟然去除了大量冗余的代码,唯一需要调整并且需要思考的就是如何区分 SSR 跟 CSR就可以了(具体的后面再聊)。
// 调整后的代码 大致
// 组件
const clickPage = (page) => {
if (pathName.includes('?')) {
history.push(`${pathName}&page=${page}`);
} else {
history.push(`${pathName}?page=${page}`);
}
};
<Pagination.Item
href={href}
onClick={(e) => {
e.preventDefault();
clickPage();
}}
>
页码
</Pagination.Item>
// 页面内
useEffect(()=> {
// 次数省略了区分服务端跟客户端渲染的条件
loadDataByPage(page)
}, [page])
SSR 与 CSR 界限的区分
这个问题是由于上面的改造所带来的思考,怎么区分这个条件?区分了怎么样才是最好的方式?其实到现在我觉得也只是做到了去如何区分条件,有没有更好的方式暂时还没有正确的思路。
当时的思考
- 增加一个标识,SSR 跟 CSR 的时候分别去改变这个标识。
- 利用现有的数据条件去做区分,只需要保存数据的时候将路由参数保存即可。
对于第一种方法,优点能够严格区分出来两种情况,但是需要增加额外字段。而且标识需要频繁更改,并且每个页面都需要增加,即使放到公共数据里,每个页面也需要单独引用,工程量比较大,并且会使代码的可读性变差,并且页面逻辑,参数多的时候,修改的地方也多,容易出问题,在尝试了一些页面后选择了放弃这种方式。
对于第二种方法,只需要在存储数据的时候,增加路由参数即可,只需要调整存数据的方法,页面内引用以及请求方法都不需要调整,但是触发逻辑需要写严谨一些,否则容易造成重复请求,资源浪费。
继续拿分页举例
// 将触发方法放在页面内,能够触发这里的一定是 CSR, 因为 SSR 请求不在这里触发
// 改造后 Query.page 路由携带的参数 dataPage页面存储的当前分页数据
useEffect(() => {
// 处理 pagenation 分页
// 增加这些条件判断 是为了防止 SSR 模式后 进入浏览器又触发一遍请求
if (Query.page && dataPage !== Number(Query.page)) {
loadDataByPage(Query.page);
}
if (dataPage > 1 && !Query?.page
) {
loadDataByPage(1);
}
}, [Query.page]);
到这里修改一个浏览器回退页面数据不正确的Bug已经解决掉了,这些事情的前因后果已经讲明白了,自己也算是弥补了上次没说好的遗憾跟懊悔。
可能只看这里的例子你感觉也不是很复杂的逻辑,但是这其中一个条件的改变,对于单一的列表页确实也只需要修改这么多久够了,但是我们有一个非常复杂的页面(个人主页),路由组合,外加分页,筛选条件等组合起来有几十种的变化,要处理好这些需要组好仔细的区分,也是相当复杂的,而且在处理这些条件时,尽量选择单一条件单一处理,避免同时依赖多个条件,否则会造成额外的影响因素,这些都是一点一点的调试出来的。
结尾
日常多思考和积累,避免临时抱佛脚,才不会关键时刻卡壳,心里素质有待加强,只是一普普通通的谈话为什么会这么紧张?亡羊补牢,为时未晚,对于自己的不足要做到及时补充,加油打工人!!!
题外话
大多数程序员其实都在处理业务,如何从业务中体现价值?或者说在汇报的时候如何去展示业务中的亮点?当你觉得处理的业务没什么亮点又该怎么取讲?欢迎大佬给出建议跟讨论!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。