我正在开发一个钩子,用于跟踪用户向下滚动页面的最大深度。 我想在 useEffect 的清理函数中使用这个值,以便我可以在卸载组件时记录该值。
该钩子似乎有效,但 Home 组件内的实现却无效。如果我填充依赖项数组,它会在滚动时连续记录,或者在组件卸载时记录一个 0。
我希望它在组件卸载时记录单个正确的值。
谢谢。
import { useEffect, useRef } from 'react';
export const useTrackScrollDepth = () => {
const scrollDepthRef = useRef(0);
const trackScrollDepth = () => {
const scrolledValue = window.scrollY;
scrollDepthRef.current = Math.max(scrolledValue, scrollDepthRef.current);
};
useEffect(() => {
const handleScrollTracking = () => {
trackScrollDepth();
};
document.addEventListener('scroll', handleScrollTracking);
return () => {
document.removeEventListener('scroll', handleScrollTracking);
};
}, []);
return scrollDepthRef.current;
};
然后在我的组件中,我这样使用钩子:
import React, { useEffect } from 'react';
import { useTrackScrollDepth } from 'hooks/useTrackScrollDepth';
export function Home() {
const maxScrollDepth = useTrackScrollDepth();
useEffect(
() => () => {
console.log(`Max scroll depth: ${maxScrollDepth}`);
},
[],
);
return (
<div>Home placeholder</div>
);
}
当您在挂钩中使用
return scrollDepthRef.current;
时,它会返回每个渲染的当前编号。由于钩子不会导致重新渲染,因此该数字保持为 0。
当您将其添加到
useEffect
的依赖项时,滚动深度发生变化,并且某些原因导致重新渲染,useEffect
清理被触发,并显示之前的值。
而是返回引用本身
return scrollDepthRef;
,并在useEffect
中使用它。
const { useEffect, useRef, useCallback, useState } = React;
const useTrackScrollDepth = () => {
const scrollDepthRef = useRef(0);
useEffect(() => {
const handleScrollTracking = () => {
const scrolledValue = window.scrollY;
scrollDepthRef.current = Math.max(scrolledValue, scrollDepthRef.current);
};
document.addEventListener('scroll', handleScrollTracking);
return () => {
document.removeEventListener('scroll', handleScrollTracking);
};
}, []);
return scrollDepthRef; // return the reference and not the current value
};
function Home() {
const scrollDepthRef = useTrackScrollDepth();
useEffect(() => () => {
console.log(`Max scroll depth: ${scrollDepthRef.current}`); // get the current value
}, [scrollDepthRef]); // use the ref as dependency
return (
<div className="home">Home placeholder</div>
);
}
const Demo = () => {
const [show, setShow] = useState(true);
const toggle = () => setShow(s => !s);
return (
<div>
<button onClick={toggle}>Toggle</button>
{show && <Home />}
</div>
);
};
ReactDOM
.createRoot(root)
.render(<Demo />);
button {
position: fixed;
}
.home {
height: 200vh;
background: red;
}
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="root"></div>
另一种选择是从钩子返回一个记忆函数,并在卸载时调用它:
const { useEffect, useRef, useCallback, useState } = React;
const useTrackScrollDepth = () => {
const scrollDepthRef = useRef(0);
useEffect(() => {
const handleScrollTracking = () => {
const scrolledValue = window.scrollY;
scrollDepthRef.current = Math.max(scrolledValue, scrollDepthRef.current);
};
document.addEventListener('scroll', handleScrollTracking);
return () => {
document.removeEventListener('scroll', handleScrollTracking);
};
}, []);
return useCallback(() => scrollDepthRef.current, []); // return a memoized function that returns the current value
};
function Home() {
const getMaxScrollDepth = useTrackScrollDepth();
useEffect(() => () => {
console.log(`Max scroll depth: ${getMaxScrollDepth()}`); // get the current value
}, [getMaxScrollDepth]);
return (
<div className="home">Home placeholder</div>
);
}
const Demo = () => {
const [show, setShow] = useState(true);
const toggle = () => setShow(s => !s);
return (
<div>
<button onClick={toggle}>Toggle</button>
{show && <Home />}
</div>
);
};
ReactDOM
.createRoot(root)
.render(<Demo />);
button {
position: fixed;
}
.home {
height: 200vh;
background: red;
}
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="root"></div>