我正在尝试使用react钩子将状态组件(即Gatsby Layout组件)转换为功能组件,以管理状态。我的目标是一旦我们到达页面中的某个点并向上滚动,便会打开一个模式。
我面临的问题是,我传递给此Layout组件(TrustedInView,请参见下面的代码)的一个道具正在更改渲染之间的值,如控制台日志所示。所以我对那里发生的事情感到困惑。我希望prop的值始终是相同的,因为index.js中记录的值没有更改(并且不应更改)。
这是布局组件中的代码:
// Hook
const usePrevious = value => {
const ref = useRef();
// Store current value in ref
useEffect(() => {
ref.current = value;
}, [value]); // Only re-run if value changes
// Return previous value (happens before update in useEffect above)
return ref.current;
};
let notOpenedYet = true;
// Layout component
const Layout = ({ intl, children, trustedInView }) => {
const [modalIsOpen, setOpen] = useState(false);
// Get the previous value (was passed into hook on last render)
let prevPosition = usePrevious(window.scrollY);
const handleNavigation = e => {
const custWindow = e.currentTarget;
if (prevPosition > custWindow.scrollY) {
console.log('trustedInView when goes up:', trustedInView);
if (trustedInView && notOpenedYet) {
notOpenedYet = false;
setTimeout(() => setOpen(true), 1500);
}
}
prevPosition = custWindow.scrollY;
};
useEffect(() => {
// Add event listener when component mounts:
window.addEventListener('scroll', e => handleNavigation(e));
// Remove event listener when component unmounts:
return window.removeEventListener('scroll', e => handleNavigation(e));
});
这是带有布局组件的index.js渲染方法:
render() {
const { intl } = this.props;
const { activeVideo, tabs, tabNumber, trustedInView } = this.state;
const features = [
// some objects
];
console.log('trustedInView in index :', trustedInView);
return (
<Layout trustedInView={trustedInView}>
这是控制台日志:
I expect the value of the prop to always be the same
任何人都能让我步入正轨吗?
答案 0 :(得分:1)
每个渲染器中都会设置一个新的侦听器,并且永远不会删除它。因此,在随后的滚动中,您实际上在每个事件中看到一个以上的console.log,其中一些通过闭包捕获了一个不同的trustedInView
,从而使该值似乎在变化,而不是在变化。
此代码存在几个问题
useEffect
没有任何依赖性,因此在每个渲染器上都被调用useEffect
removeEventListener
并没有在组件卸载时真正被调用,useEffect期望您返回一个函数,并且无论removeEventListener的返回结果如何。removeEventListener
而不是传递给addEventListener
每次创建的另一个函数,因此不会删除任何侦听器。handleNavigation
的新版本,因此即使我们要修复先前的错误,也必须确保删除正确的侦听器。幸运的是,这就是useCallback
的目的。考虑到这些因素,看起来会像这样:
const handleNavigation = useCallback(() => { /* ... */ }, [prevPosition, trustedInView])
useEffect(() => {
window.addEventListener('scroll', handleNavigation);
return () => {
window.removeEventListener('scroll', handleNavigation);
}
}, [handleNavigation])
但是,即使这是有问题的,因为prevPosition
每次破坏效果都会发生变化,加上会因您的目的而变得陈旧。因此,最好直接使用没有您的usePrevious
钩子的ref
const prevValueRef = useRef(0);
const handleNavigation = useCallback(e => {
const scrollY = e.currentTarget.scrollY;
const prevScrollY = prevValueRef.current;
// Save the value for the next event
prevValueRef.current = scrollY;
// ...
}, [trustedInView])