我是Java语言的新手,目前正在Docker getting-started tutorial中尝试使用Demo应用程序。该应用程序是一个简单的待办事项列表,您可以在其中添加和删除项目。
我试图在页面的每个实例上更新列表,而不必重新加载页面。
我已经设法编辑节点 express 服务器,以便它通过服务器发送的事件发送更新。
问题:
前端使用React。当前显示项目的数据包含在ìtems
数组中。onNewItem
将项目添加到该数组。但是,当从onNewItem
调用onmessage
时,即使从其他React组件调用onNewItem
时,items数组也为null。如何访问items
数组的初始化版本? (它由1.useEffect初始化,它从服务器获取项目。)
下面是代码的一部分
function TodoListCard() {
const [items, setItems] = React.useState(null);
const [ listening, setListening ] = React.useState(false);
React.useEffect(() => {
fetch('/items')
.then(r => r.json())
.then(setItems);
}, []);
React.useEffect( () => {
if (!listening) {
const events = new EventSource('/events/subscribe');
events.onmessage = (event) => {
const parsedData = JSON.parse(event.data);
switch (parsedData.type) {
case "add":
var newItem = {id: parsedData.id, name: parsedData.name, completed: parsedData.completed};
onNewItem(newItem);
break;
default:
break;
}
};
setListening(true);
}
}, [listening]);
const onNewItem = React.useCallback(
newItem => {
if (items.some(e => e.id === newItem.id)){return;}
setItems([...items, newItem]);
},
[items],
);
最佳答案
让我们从出现问题的原因开始。问题是,当您调用onNewItem(newItem)
时,您使用的是对onNewItem
的过时引用。因此,函数内的items
仍将设置为初始值。
您通过为React.useCallback
提供依赖项数组来部分解决此问题。当onNewItem
的新值可用时,这将更新items
。但是,由于React.useEffect
并未将onNewItem
列为依赖项,因此它继续使用旧版本的onNewItem
。
如此说来,您可以考虑将onNewItem
添加到React.useEffect
的依赖项数组中。尽管这是正确的操作,但仅将其添加到依赖项数组是不够的。
将onNewItem
添加到React.useEffect
的依赖数组中会遇到什么问题?没有清除功能,因此您将使用不同的onmessage
处理程序(不同版本的onNewItem
)多次订阅该 channel 。
因此,考虑到以上所有因素,解决方案可能看起来像这样:
function TodoListCard() {
const [items, setItems] = React.useState(null);
const [events, setEvents] = React.useState(null);
React.useEffect(() => {
const pEvents = fetch('/items')
.then(r => r.json())
.then(setItems)
.then(() => new EventSource('/events/subscribe'));
pEvents.then(setEvents);
return () => pEvents.then(events => events.close());
}, []);
React.useEffect(() => {
if (!events) return;
events.onmessage = (event) => {
const parsedData = JSON.parse(event.data);
switch (parsedData.type) {
case "add":
var newItem = {
id: parsedData.id,
name: parsedData.name,
completed: parsedData.completed
};
onNewItem(newItem);
break;
default:
break;
}
};
}, [events, onNewItem]);
const onNewItem = React.useCallback(newItem => {
const isPresent = items.some(item => item.id === newItem.id);
if (isPresent) return;
setItems([...items, newItem]);
}, [items]);
return (
// ...
);
}
我将EventSource
的创建移到了第一个React.useEffect
内,因为仅在组件安装后才需要发生(并且在卸载时需要关闭连接)。空的依赖项数组只会在安装时调用该函数,而在卸载时调用清除函数。现在,第二个
React.useEffect
具有依赖项数组[events, onNewItem]
,因为设置events
时需要附加onmessage
处理程序。并且,如果onNewItem
回调更新到新版本,则应将其附加为新的onmessage
处理程序(替换旧的处理程序)。由于已经处理了打开和关闭events
的操作,因此不再需要清理功能。虽然上面应该做的。如果管理特定状态变得越来越复杂,则最好选择
useReducer
而不是useState
。function reducer(items, action) {
switch (action.type) {
case "add":
const isPresent = items.some(item => item.id == action.item.id);
if (isPresent) return items;
return [...items, action.item];
case "replace all":
return action.items;
case "complete": // <- unused example case
return items.map(item => {
if (item.id != action.id) return item;
return {...item, completed: true};
});
// ...
default: // silently ignore unsupported operations
return items;
}
}
function TodoListCard() {
const [items, dispatch] = React.useReducer(reducer, null);
React.useEffect(() => {
const pEvents = fetch('/items')
.then(r => r.json())
.then(items => dispatch({type: "replace all", items}))
.then(() => new EventSource('/events/subscribe'));
pEvents.then(events => {
events.onmessage = (event) => {
const {type, ...item} = JSON.parse(event.data);
dispatch({type, item});
};
});
return () => pEvents.then(events => events.close());
}, []);
// if you still need onNewItem for your render:
const onNewItem = React.useCallback(item => {
dispatch({type: "add", item});
}, []);
return (
// ...
);
}
上面的代码将所有items
管理逻辑提取到“reducer”函数中。 React保证dispatch
返回的useReducer
函数是稳定的,因此您可以从依赖项数组中忽略它(但不必这样做)。关于javascript - 如何在useEffect中使用更新的值,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/64376266/