在组件更新时在Redux中保存React组件滚动位置



我有一个React组件,当组件卸载时,它使用钩子来保存组件的滚动位置。这非常有效,但在不卸载组件的情况下从一组数据导航到另一组数据时会失败。

例如,想象一下Slack界面,左边有一个消息频道的侧边栏,右边是一个消息列表(messageList(。如果要在两个通道之间导航,messageList组件将使用messageList的一组新数据进行更新,但该组件从未卸载,因此滚动位置永远不会保存。

我想出了一个有效的解决方案,但也发出了警告。

当消息列表ID更改时,我当前使用的组件的效果挂钩(精简(和当前保存滚动位置的代码:

// Component...
const usePrevious = (value) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
// Save scroll position when Component unmounts
useEffect(() => {    
return () => {
setScrollOffset(parent._id, scrollPos.current);
};
}, []);
// Save scroll position when Parent ID changes
const oldParent = usePrevious(parent);
if (oldParent && parent._id !== oldParent._id) {
setScrollOffset(oldParent._id, list ? list.scrollTop : 0);
}
// ...Component

这会引发以下错误:Warning: Cannot update a component from inside the function body of a different component.

导致它的行是最后一个if块内部的setScrollOffset调用。我想,虽然这是有效的,但这不是我应该处理这类事情的方式。当组件上的特定道具发生变化时,处理保存滚动位置的更好方法是什么?

parent._id添加到依赖数组中。重构代码以仍然缓存上一个父id,将其添加到依赖项中,并在效果中移动条件测试。

清理效果

清除功能在从UI中删除组件之前运行以防止内存泄漏。此外,如果组件渲染多个次(就像他们通常做的那样(,之前的效果在执行下一个效果

// Return previous parent id and cache current
const oldParent = usePrevious(parent);
// Save scroll position when Component unmounts or parent id changes
useEffect(() => {
if (oldParent && parent._id !== oldParent._id) {
setScrollOffset(oldParent._id, list ? list.scrollTop : 0);
}
return () => {
setScrollOffset(parent._id, scrollPos.current);
};
}, [parent._id, oldParent]);

如果不太符合要求,则使用两种效果,一种用于装载/卸载,另一种仅用于更新父id。

感谢@draw reese的建议,他让我找到了正确的道路。在采用了他的解决方案(以前我无法正常工作(后,我能够将我的问题与react路由器的使用隔离开来。(在我的情况下连接了react路由器(。问题是该组件正在渲染并启动onScroll事件处理程序,并在我读取之前覆盖我的滚动位置

对我来说,解决方案最终是保留我现有的useEffect挂钩,但将滚动偏移保存从挂钩中拉出来,放入useLayoutEffect中(必须保留useEffect,因为useEffect中还有其他东西是我为了保持示例代码的精简而删除的(。useLayoutEffect允许我在组件触发onScroll事件之前读取当前滚动位置,该事件最终将我保存的滚动位置引用覆盖为0。

实际上,这完全消除了对usePrevious钩子的需求,使我的代码总体上更干净了。我的useLayoutEffect钩子现在看起来是这样的:

useLayoutEffect(() => {
return () => {
setScrollOffset(parent._id, scrollPos.current);
};
}, [parent._id]);

最新更新