如何解决React:在useEffect中调用上下文函数时防止无限循环
在我的react应用中,我正在渲染<Item>
组件的不同实例,并且希望它们在Context中注册/注销,具体取决于当前是否已安装它们。
我使用两个上下文(ItemContext
提供了注册的项目,ItemContextSetters
提供了注册/注销的功能)。
const ItemContext = React.createContext({});
const ItemContextSetters = React.createContext({
registerItem: (id,data) => undefined,unregisterItem: (id) => undefined
});
function ContextController(props) {
const [items,setItems] = useState({});
const unregisterItem = useCallback(
(id) => {
const itemsUpdate = { ...items };
delete itemsUpdate[id];
setItems(itemsUpdate);
},[items]
);
const registerItem = useCallback(
(id,data) => {
if (items.hasOwnProperty(id) && items[id] === data) {
return;
}
const itemsUpdate = { ...items,[id]: data };
setItems(itemsUpdate);
},[items]
);
return (
<ItemContext.Provider value={{ items }}>
<ItemContextSetters.Provider value={{ registerItem,unregisterItem }}>
{props.children}
</ItemContextSetters.Provider>
</ItemContext.Provider>
);
}
<Item>
组件在挂载或props.data
更改时应自行注册,而在卸载时应注销。因此,我认为可以使用useEffect
非常干净地完成此操作:
function Item(props) {
const itemContextSetters = useContext(ItemContextSetters);
useEffect(() => {
itemContextSetters.registerItem(props.id,props.data);
return () => {
itemContextSetters.unregisterItem(props.id);
};
},[itemContextSetters,props.id,props.data]);
...
}
完整示例请参见此codesandbox
现在,问题在于这给了我无限循环,我不知道如何做得更好。循环是这样发生的(我相信):
- 有
<Item>
个电话registerItem
- 在上下文中,
items
被更改,因此registerItem
被重新构建(因为它取决于[items]
- 这会触发
<Item>
中的更改,因为itemContextSetters
已更改,并且useEffect
被再次执行。 - 还执行了先前渲染的清理效果! (如here所述:“ React还会在下次运行效果之前清除以前的渲染中的效果”)
- 这再次更改了上下文中的
items
- 依此类推...
我真的想不出一个可以避免此问题的干净解决方案。我是否滥用任何挂钩或上下文API?您可以通过任何一般模式帮助我如何编写由组件在其useEffect
主体和useEffect
-cleanup中调用的这种注册/注销上下文吗?
我不想做的事情
- 完全摆脱上下文。在我的真实应用程序中,结构更加复杂,整个应用程序中的不同组件都需要此信息,因此我相信我要坚持上下文
- 避免将
<Item>
组件从dom中强制删除(使用{renderFirstBlock &&
),而应使用状态isHidden
之类的东西。在我的真实应用中,这是我目前无法更改的任何内容。我的目标是跟踪所有现有组件实例的data
。
谢谢!
解决方法
您可以使设置器具有稳定的引用,因为它们确实不需要依赖items
:
const ItemContext = React.createContext({});
const ItemContextSetters = React.createContext({
registerItem: (id,data) => undefined,unregisterItem: (id) => undefined
});
function ContextController(props) {
const [items,setItems] = useState({});
const unregisterItem = useCallback(
(id) => {
setItems(currentItems => {
const itemsUpdate = { ...currentItems };
delete itemsUpdate[id];
return itemsUpdate
});
},[]
);
const registerItem = useCallback(
(id,data) => {
setItems(currentItems => {
if (currentItems.hasOwnProperty(id) && currentItems[id] === data) {
return currentItems;
}
return { ...currentItems,[id]: data }
} );
},[]
);
const itemsSetters = useMemo(() => ({ registerItem,unregisterItem }),[registerItem,unregisterItem])
return (
<ItemContext.Provider value={{ items }}>
<ItemContextSetters.Provider value={itemsSetters}>
{props.children}
</ItemContextSetters.Provider>
</ItemContext.Provider>
);
}
现在您的效果应该可以预期了
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。