问题描述
在我的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>
);
}
现在您的效果应该可以预期了