我有一个 React 应用程序(由 Next.js 提供支持,并在 TypeScript 中实现),其中一个组件中包含一个由 api 支持的数据集。该组件的 api 数据可以无限期地缓存,因为它是只读数据集,永远不会在服务器上更改。网上有很多关于如何准确执行此操作的好示例,以及更复杂的完整 CRUD 应用程序。这些应用程序在服务器和客户端上维护一组数据,使它们在两者之间保持同步。对于应该从服务器呈现完整数据集的应用程序来说,这项工作很好。我还看到了当服务器数据集很大时进行分页的示例。
我现在正在尝试构建一个组件,其功能需要与我迄今为止找到的示例略有不同:
在示例中,我看到重新渲染反应组件(在新数据上)的触发器似乎要求该组件是从状态变量数组构建的,该数组本身在获取器内设置(覆盖)。我认为我不能在那里使用状态变量,因为 useState 实现了相应的 set* 方法来简单地覆盖状态变量的数据,而不是添加它。我最初尝试使用两个变量:一个状态变量填充有新消息,另一个状态变量包含接收到的数据的超集,但是我没有看到在更新其构建的状态变量后重新渲染反应组件来自(packetsToRender)。
我当前的代码如下:
React 组件代码
import { useState, useEffect } from 'react';
var packets: PacketSummary[] = [];
var latestPacketId: number = -1;
function CommsPacketList({}: {}) {
const rows: any[] = [];
const [fetchError, setFetchError] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [packetsToRender, setPacketsToRender] = useState(packets);
var url: string;
function addPacketsToArray(newPackets: PacketSummary[]) {
var lastId = -1;
if (newPackets.length == 0) return
newPackets.forEach((packetSummary: PacketSummary) => {
packets.push(packetSummary);
lastId = packetSummary.packet_index;
});
latestPacketId = lastId;
setPacketsToRender(packets);
console.log('Added packets to array. latestPacketId = ', lastId);
}
useEffect(() => {
const fetchNewItems = async () => {
try {
url = API_ENDPOINT__COMMS_PACKET_LIST + '?limit=100&offset_index=' + latestPacketId;
const response = await fetch(url);
const data = await response.json();
addPacketsToArray(data);
setFetchError(null);
} catch (error: any) {
setFetchError(error.message);
} finally {
setIsLoading(false);
}
setTimeout(() => {
fetchNewItems();
}, 5000);
}
(async () => await fetchNewItems())();
}, []);
if (!packetsToRender && fetchError) return <div>Failed to load. Error: {fetchError} </div>
if (!packetsToRender && isLoading) return <div>Loading...</div>
if (!packetsToRender) return <div>No packets!</div>
packetsToRender.forEach((packetSummary: PacketSummary) => {
rows.push(
<MessagePacket
packetSummary={packetSummary}
/>
);
});
return (
<div className={styles.comms_packet_listing}>
{rows}
</div>
);
}
使用此代码我遇到以下问题:
packetsToRender
对 addPacketsToArray
的所有后续更新不会产生任何组件重新渲染,因此只有第一条消息可见。 (我添加了 console.log 输出来验证 packets
和 addPacketsToArray
是否正在更新,并且 packetsToRender.forEach...
没有在初始加载后被调用)offset_index=-1
的初始请求被发出两次,所有后续请求也是以 5 秒的间隔发出。setTimeout
来实现轮询,但我想知道这是否是一种可行的方法?在获取器内部执行此操作似乎有点老套,只是对那里的良好实践感到好奇(!)packets
和lastPacketId
),我的代码编辑器告诉我,TypeScript 领域对此不以为然(我对此同样很陌生——再说一次,这是一个“良好实践”问题)关于
setTimeout
的替代方案:我已经研究过axios和useSWR,但还没有找到任何可以帮助我的东西。 (useSWR 能够设置 refreshInterval
,但在我的测试中,我发现我无法以超过每秒一次的速度驱动更新 - 而我需要更新运行得更快一点 - 目标是每 0.1 秒)
在使用生成式 AI 进行进一步迭代后,我能够解决上述问题(以及 Wiktor 提供的非常有用的提示 - 谢谢!)
function useInterval(callback: () => void, delay: number | null) {
const savedCallback = React.useRef<() => void>();
React.useEffect(() => {
savedCallback.current = callback;
}, [callback]);
React.useEffect(() => {
function tick() {
savedCallback.current?.();
}
if (delay !== null) {
const intervalId = setInterval(tick, delay);
return () => clearInterval(intervalId);
}
}, [delay]);
}
function CommsPacketList({}: {}) {
const rows: any[] = [];
var packets: PacketSummary[] = [];
var url: string;
const [fetchError, setFetchError] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [packetsToRender, setPacketsToRender] = useState(packets);
const [latestPacketId, setLatestPacketId] = useState<number>(-1);
const addPacketsToArray = (newPackets: PacketSummary[]) => {
if (!newPackets.length) return
const updatedPackets = [...packetsToRender, ...newPackets];
const lastId = newPackets.length > 0 ? newPackets[newPackets.length - 1].packet_index : latestPacketId;
setLatestPacketId(lastId);
setPacketsToRender(updatedPackets);
}
const fetchNewItems = async () => {
try {
url = API_ENDPOINT__COMMS_PACKET_LIST + '?limit=100&offset_index=' + latestPacketId;
const response = await fetch(url);
const data = await response.json();
addPacketsToArray(data);
setFetchError(null);
} catch (error: any) {
setFetchError(error.message);
} finally {
setIsLoading(false);
}
}
// Use the custom useInterval hook
useInterval(() => {
fetchNewItems();
}, 200);
// Handle no packets case
if (!packetsToRender && fetchError) return <div>Failed to load. Error: {fetchError} </div>
if (!packetsToRender && isLoading) return <div>Loading...</div>
if (!packetsToRender) return <div>No packets!</div>
// Render packets
packetsToRender.forEach((packetSummary: PacketSummary) => {
rows.push(
<MessagePacket
packetSummary={packetSummary}
/>
);
});
return (
<div className={styles.comms_packet_listing}>
{rows}
</div>
);
}
我现在已经能够将间隔减少到 200 毫秒,一切都按预期进行。