
本文详解如何利用 useSWR 的 compare 选项,在自动轮询(如每30秒)场景下,安全、可靠地获取前后两次请求的数组长度差值,并基于该差值驱动增量渲染逻辑。
本文详解如何利用 useswr 的 `compare` 选项,在自动轮询(如每30秒)场景下,安全、可靠地获取前后两次请求的数组长度差值,并基于该差值驱动增量渲染逻辑。
在 Next.js 应用中使用 useSWR 实现定时轮询(如 refreshInterval: 30000)时,一个常见需求是:当后端返回的消息数组(如 messages)每次新增 1–4 条记录,我们需要精确感知本次 revalidation 带来的新增数量,而非简单依赖 messages.length 的变化触发重渲染——因为 useEffect 监听 messages 时,无法直接访问“上一次的 length”,且在组件挂载初期 prevMessagesLength 为 undefined,易导致计算错误或副作用失控。
官方 useSWR API 提供了关键能力:compare 配置项。它是一个接收 (prevData, newData) 的纯函数,会在每次新数据到达时被调用,用于判断是否应触发状态更新与重新渲染。更重要的是:它天然暴露前后两次数据快照,是计算差异的理想入口。
以下为完整、健壮的实现方案:
import useSWR, { SWRConfiguration } from 'swr';
const fetcher = (url: string) => fetch(url).then(r => r.json());
const MyMessageFeed = () => {
const [newItemCount, setNewItemCount] = useState(0);
// 自定义比较函数:计算 messages 数组长度差,并同步更新状态
const compare: SWRConfiguration['compare'] = (prevData, newData) => {
// 安全处理:确保两者都存在且为对象,且包含 messages 数组
if (!prevData || !newData || !Array.isArray(prevData.messages) || !Array.isArray(newData.messages)) {
return false;
}
const prevLen = prevData.messages.length;
const newLen = newData.messages.length;
const diff = Math.max(0, newLen - prevLen); // 新增数量(非负)
setNewItemCount(diff);
// 返回 true 表示数据有实质变化,触发 re-render;此处按需决定(通常建议返回 diff > 0)
return diff > 0;
};
const { data, error, isLoading } = useSWR(
'/api/messages',
fetcher,
{
refreshInterval: 30000,
compare, // ✅ 关键:注入差异计算逻辑
// 可选:启用 keepPreviousData 避免闪烁(尤其在差值为 0 时)
keepPreviousData: true,
}
);
// 增量展示逻辑(示例):将新增消息逐条淡入
useEffect(() => {
if (newItemCount === 0 || !data?.messages) return;
const messages = data.messages;
const startIndex = messages.length - newItemCount;
const showNext = (index: number) => {
if (index >= messages.length) return;
// 此处可触发 UI 动画、添加到列表等
console.log('Showing new message:', messages[index]);
// 按比例分配 30s 内的展示时间(如 3 条则每 10s 一条)
const delay = (30000 / newItemCount) * (index - startIndex + 1);
setTimeout(() => showNext(index + 1), delay);
};
showNext(startIndex);
}, [newItemCount, data]);
if (error) return <div classname="error">加载失败</div>;
if (isLoading || !data) return <div>加载中...</div>;
return (
<div>
<p>当前消息总数:<strong>{data.messages.length}</strong></p>
<p>本次新增:<strong>{newItemCount}</strong> 条</p>
{/* 渲染完整消息列表 */}
<ul>
{data.messages.map((msg: any, i: number) => (
<li key="{i}">{msg.content}</li>
))}
</ul>
</div>
);
};
export default MyMessageFeed;
✅ 关键要点说明:
-
compare函数在 SWR 内部数据比对阶段执行,早于 React 渲染周期,因此可安全调用setNewItemCount触发后续逻辑; - 必须做空值与类型防护(
prevData?.messages),避免首次加载或请求失败时崩溃; -
keepPreviousData: true是推荐搭配项:它让data在新数据加载中保持旧值,使 UI 过渡更平滑; -
newItemCount是派生状态,不应直接用于条件渲染主列表(仍应以data.messages为准),而是驱动动画、提示、统计等辅助行为; - 若需更高精度(如识别具体新增了哪几条),可在
compare中实现基于 ID 的 diff 算法(如Set差集),但对长度差场景,上述方案已足够高效可靠。
通过 compare 钩子,你不再需要手动维护 ref 或 useState 来“记住”上一次数据——SWR 已为你准备好最及时、最准确的对比上下文。这是声明式数据流中处理增量更新的优雅范式。










