在我的反应应用程序中,我正在渲染<Item>
组件的不同实例,我希望它们在上下文中注册/取消注册,具体取决于它们当前是否已安装。
我正在使用两个上下文(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]);
...
}
完整示例请参阅此代码框
现在,问题是这给了我一个无限循环,我不知道如何做得更好。循环是这样发生的(我相信):
- 一个
<Item>
电话registerItem
- 在上下文中,
items
被改变,因此registerItem
被重建(因为它取决于[items]
- 这会触发更改,
<Item>
因为itemContextSetters
已更改并useEffect
再次执行。 - 还执行了先前渲染的清理效果!(如此处所述:“React 还会在下次运行效果之前清理上一次渲染中的效果”)
- 这再次
items
在上下文中改变 - 等等 ...
我真的想不出一个可以避免这个问题的干净解决方案。我是否滥用了任何钩子或上下文 api?useEffect
关于如何编写组件在其-body和useEffect
-cleanup中调用的此类注册/注销上下文的一般模式,您能帮我吗?
我不想做的事情:
- 完全摆脱上下文。在我的真实应用程序中,结构更复杂,整个应用程序的不同组件都需要这些信息,所以我相信我想坚持一个上下文
<Item>
避免从 dom中硬删除组件(使用{renderFirstBlock &&
),而是使用类似状态的东西isHidden
。在我的真实应用程序中,目前我无法更改。我的目标是跟踪data
所有现有的组件实例。
谢谢!