记一次随机渲染问题

通过种子随机保证 SSR 和 CSR 环境随机结果一致

背景

PD: 兆兆啊,你给我做个开关,打开开关后这个列表随机排序渲染

兆兆: 简单简单,这不分分钟搞定

然后我写了一下午……

preview

SSR & CSR

第一版代码如下,通过 Math.random() - 0.5 是正数或负数控制每次的排序顺序:

/** 随机排序 */
const renderList = coverList
//合并封面列表和直播列表
.map((coverItem) => {
const liveItem = liveList.find(
({ liveId }) => liveId === coverItem?.liveId
);
return { ...liveItem, ...coverItem };
})
//随机排序
.sort(() => (isRandom ? Math.random() - 0.5 : 1));

代码发上去一看有问题,页面渲染后会闪屏,体验很差。

原因是 SSR 和 CSR 环境下都会随机排序一次,并且结果不一样,导致 hydrate 时两份 DOM 映射不上触发整体重渲染。

有两个解法:

  1. 架上一层 FaaS 在数据源上做随机排序,保证下发的数据完全一致;
  2. 控制随机种子,确保 SSR 和 CSR 下生成同一个随机数,随机排序的结果自然也就一致了;

种子随机

写 FaaS 太麻烦了还容易背锅,所以采用种子随机改了一版:

/** 种子随机 */
const random = (seed) => {
seed = (seed * 9301 + 49297) % 233280;
return seed / 233280.0;
};

/** 随机排序 */
const renderList = coverList
//合并封面列表和直播列表
.map((coverItem) => {
const liveItem = liveList.find(
({ liveId }) => liveId === coverItem?.liveId
);
return { ...liveItem, ...coverItem };
})
//随机排序
.sort((a, b) => (isRandom ? random(serverTime + a?.liveId) - 0.5 : 1));

这里用 serverTimeid 做种子,服务端时间可以保证每次刷新页面随机一次,唯一 ID 可以保证每一次遍历随机一次。在 SSR 下 和 CSR 下种子是一致的,随机出来的结果也就一致了。

种子随机原理: https://www.zhihu.com/question/22818104

Sort

跑了一下发现现象很神奇,SSR 和 CSR 下生成的随机数确实是一致的,但是随机排序生成的列表居然不一致,还是触发了重渲染。

调了半天发线是 array.sort() 在 Node 和 Browser 环境下实现方式不一致:

console.log([-2, -1, 0, 1, 2].sort());

// Node
[ -1, -2, 0, 1, 2 ]

// Browser
[-1, -2, 0, 1, 2]

心中万马奔腾…… 改成这样就好了:

/** 种子随机 */
const random = (seed) => {
seed = (seed * 9301 + 49297) % 233280;
return seed / 233280.0;
};

/** 随机排序 */
const renderList = coverList
//合并封面列表和直播列表,挂上随机数
.map((coverItem) => {
const liveItem = plugin.find(({ liveId }) => liveId === coverItem.liveId);
return {
...liveItem,
...coverItem,
randomData: random(Number(serverTime) + liveItem.liveId),
};
})
//随机排序
.sort((a, b) => {
return a.randomData - b.randomData;
});
查看评论