我搜索了很多教程,并在谷歌上搜索了几个小时。我找不到任何东西来回答为什么 SignalR 回调看不到我的数据的当前状态,但 React 做得很好。
首先,我的代码(无论如何都是简化版本):
反应组件:
function example() {
const [data, setData] = useState<DataModel[]>([]);
useEffect(() => {
loadSignalR();
loadData();
}, []);
async function loadData() {
const serverData= await getAllData();
setData(serverData);
}
function addData(addedItem: DataModel) {
setData([...data, addedItem]);
}
function loadSignalR() {
signalRService.startConnection(`${BASE_PATH}/dataHub`);
signalRService.onDataAdded(addData);
}
return (
<div className="ps-3" style={{ minWidth: '50vw' }}>
<h2 className="w-100 text-center">Data</h2>
<DataCarouselContainer
data={data}
/>
</div>
);
}
和信号RService:
const signalRService = {
connection: null,
startConnection: hubUrl => {
signalRService.connection = new HubConnectionBuilder()
.withUrl(hubUrl)
.build();
signalRService.connection
.start()
.then(() => signalRService.addToGroup('QT'))
.then(() => {
console.log('SignalR connection established.');
})
.catch(err => {
console.error('Error starting SignalR connection:', err);
});
},
onDataAdded: callback => {
signalRService.connection.on('DataAdded', serverData=> {
callback(serverData);
});
},
addToGroup: groupName => {
signalRService.connection.invoke('AddToGroup', groupName).catch(err => {
console.error('Error adding to group:', err);
});
},
};
export default signalRService;
后端工作正常。它发送事件并由前端接收它们。一切似乎都正常,但是当调用 addData 时,它将数据视为空数组。我的屏幕显示它并不是真正空的,因为我可以看到屏幕上列出了 4 个项目。
那么我做错了什么?为什么 SignalR 只将数据状态视为空数组?
好消息/坏消息是,在我的谷歌搜索中,我找到了具有相同问题的其他人的解决方法。如果我创建第二个一次性变量,请说:
const [newData, setNewData] = useState<TaskModel[]>([]);
然后添加一个新的useEffect,例如:
useEffect(() => {
setData([...data, ...newData]);
}, [newData]);
它有效。 newData 成为一次性数据,而 React 将数据视为当前状态。所以在 useEffect 中我可以更新数据并显示它。
问题是,它很混乱,如果我想做更多事件,比如删除数据,我需要添加更多废弃代码和更多 useEffects。它很容易失控,我讨厌这样的想法。我只是希望 SignalR 能够正确查看当前状态,而不必每次数据更改时都创建新连接。
我尝试分离代码。我尝试直接、间接、甚至使用事件数组设置数据。
我尝试使用单个 useEffect 无济于事(创建了无限循环)。
除了一次性代码之外,似乎什么都不起作用。
JavaScript 闭包是您遇到问题的原因。它总是看到一个空数组,因为当您从 SignalR 回调中调用 addData 时,它会记录注册回调时的数据状态。
这是一种更有效的方法来处理该问题,而无需创建第二个状态:
更新您的组件以使用参考:
function example() {
const [data, setData] = useState<DataModel[]>([]);
const dataRef = useRef<DataModel[]>([]);
useEffect(() => {
loadSignalR();
loadData();
}, []);
async function loadData() {
const serverData = await getAllData();
setData(serverData);
dataRef.current = serverData;
}
function addData(addedItem: DataModel) {
setData(prevData => {
const updatedData = [...prevData, addedItem];
dataRef.current = updatedData;
return updatedData;
});
}
function loadSignalR() {
signalRService.startConnection(`${BASE_PATH}/dataHub`);
signalRService.onDataAdded(addData);
}
return (
<div className="ps-3" style={{ minWidth: '50vw' }}>
<h2 className="w-100 text-center">Data</h2>
<DataCarouselContainer data={data} />
</div>
);}
使用功能更新方法或结合 ref 和 state 可确保您的 SignalR 回调始终看到最新的状态。这种方法应该消除对一次性代码和额外 useEffect 挂钩的需要。