我正在尝试实现一个简单的标头组件,其中标头在到达某个滚动位置后会隐藏。
我想出了以下解决方案,并认为只有当headerState
的值实际更改时,才会重新渲染Header
组件,而事实并非如此。组件将在每次更改滚动位置时重新渲染,即使甚至没有调用setHeaderState
也是如此。
我在这里错过了什么?它是否因为使用useWindowScroll
而重新渲染,我可以以某种方式避免它吗?
import { useWindowScroll } from "react-use";
const useHeaderState = () => {
const [ headerState, setHeaderState ] = React.useState(0);
const { y } = useWindowScroll();
React.useEffect(() => {
if (y > 50 && headerState !== 1) {
setHeaderState(1);
}
}, [y, headerState]);
return headerState;
}
const Header: React.FC = () => {
const headerState = useHeaderState();
console.log("rerender");
return (
<header
className={cn(
"fixed top-0 left-0 w-full text-white z-30 transition-transform transition-250",
headerState === 1 && "-translate-16",
)}
>
<div className="bg-gray-900 h-16 md:h-32">header</div>
</header>
)
};
重新渲染确实是因为useWindowScroll
。 如源代码所示,每次窗口滚动时,useWindowScroll
调用 setState。
为了避免浪费渲染,您可以自己收听窗口滚动,而不是使用useWindowScroll
,例如:
const useHeaderState = () => {
const [headerState, setHeaderState] = React.useState(0);
const frame = useRef(0);
useEffect(() => {
const handler = () => {
cancelAnimationFrame(frame.current);
frame.current = requestAnimationFrame(() => {
if (window.pageYOffset > 50 && headerState !== 1) {
setHeaderState(1);
}
});
};
window.addEventListener("scroll", handler, {
capture: false,
passive: true
});
return () => {
cancelAnimationFrame(frame.current);
window.removeEventListener("scroll", handler);
};
});
return headerState;
};
就像你说的问题是,内部useWindowScroll
可能会在自己的useState
钩子上调用setState
。
通常,组件重新渲染不应该是一个问题吗?这有问题吗?
但是,一个选项是将useHeaderState
钩移动到父钩子,然后使用React.memo
来记忆Header
,并且仅在道具更改时才重新渲染。
import * as React from "react";
import { render } from "react-dom";
import { useWindowScroll } from "react-use";
const useHeaderState = () => {
const [headerState, setHeaderState] = React.useState(0);
const { y } = useWindowScroll();
React.useEffect(() => {
if (y > 50 && headerState !== 1) {
setHeaderState(1);
}
}, [y, headerState]);
return headerState;
};
const Header: React.FC<{ headerState: number }> = React.memo(props => {
console.log("rerender");
return (
<header className={props.headerState === 1 && "class"}>
<div className="bg-gray-900 h-16 md:h-32">header</div>
</header>
);
});
function App() {
const headerState = useHeaderState();
return (
<div style={{ height: 2000 }}>
<Header headerState={headerState} />
</div>
);
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);