我正在编码一个React
标头组件,它将在滚动时滑出。 (代码在底部)
我在useEffect
挂钩中分配了一个事件处理程序,当发生滚动事件时它将调用handleChange
函数。我已经将此handleChange
函数包装在useCallback
钩子中以提高性能(无需在每个渲染器上重新实例化)。
我还用React.memo
包装了整个功能。
我将滚动位置存储在useRef
中,因为更改并不需要触发重新渲染,所以重新渲染将由setVisibility
内部的handleChange
引起。
现在,此组件的渲染时间超过了我的预期。我向组件中的每个“操作”添加了console.log来跟踪正在发生的事情。我已经复制了Chrome DevTools控制台输出。 安装后,控制台将显示:
Render 0
Render 0
Added event listener
为什么它甚至在运行useEffect之前都会渲染两次?
当我在页面上滚动时,控制台显示:
Handling scroll
Previous scroll: 249.5625
Scroll: 251.5625
Render 249.5625
Render 249.5625
Handling scroll
Previous scroll: 251.5625
Scroll: 252.5625
Render 251.5625
Render 251.5625
Handling scroll
Previous scroll: 252.5625
Scroll: 254.5625
因此调用了我的handleChange
函数,但是每次滚动位置改变时,它都会重新渲染两次组件?
请帮助我弄清楚为什么当状态更改时此组件会渲染两次。 编辑:我发现它滑出或滑入时甚至可以渲染4次。
//I'm using styled-components and the prop simply tranforms the header up when the visible prop is true
function Header(): JSX.Element {
const [visible, setVisibility] = useState(true);
const scrollPosition = useRef(0);
const handleScroll = useCallback(() => {
//Non-absolute values will result in comparison errors
const currentScrollPosition = Math.abs(
document.body.getBoundingClientRect().top
);
//Testing
console.log("Handling scroll");
console.log("Previous scroll:", scrollPosition.current);
console.log("Scroll:", currentScrollPosition);
//Do not hide when scrolling down for the first time until user scroll down a little
if (currentScrollPosition <= 150) {
setVisibility(true);
} else setVisibility(scrollPosition.current > currentScrollPosition);
//Update ref value to new scrolling position
scrollPosition.current = currentScrollPosition;
}, []);
useEffect(() => {
window.addEventListener("scroll", handleScroll);
//Testing
console.log("Added event listener");
return (): void => {
window.removeEventListener("scroll", handleScroll);
//Testing
console.log("Removed event listener");
};
}, []);
//Testing
console.log("Render", scrollPosition.current);
return (
<>
<S_header visible={!visible}>
<h3>Work on preventing excessive rendering </h3>
</S_header>
</>
);
}
export default memo(Header);
答案 0 :(得分:0)
我认为因为onScroll会触发多次,所以自然地您会得到多个触发器。最佳实践是使用反跳或油门,这取决于您的需要。我在这里显示带有反跳的版本
import { debounce } from 'lodash/fp';
import React, { useEffect, useCallback, useRef, useState } from 'react';
import PropTypes from 'prop-types';
const propTypes = {};
const defaultProps = {};
const Component = ({}) => {
const [visible, setVisibility] = useState(true);
const scrollPosition = useRef(0);
const handleScroll = useCallback(debounce(200, () => {
// Non-absolute values will result in comparison errors
const currentScrollPosition = Math.abs(document.body.getBoundingClientRect().top);
// Testing
console.log('Handling scroll');
console.log('Previous scroll:', scrollPosition.current);
console.log('Scroll:', currentScrollPosition);
// Do not hide when scrolling down for the first time until user scroll down a little
if (currentScrollPosition <= 150) {
setVisibility(true);
} else setVisibility(scrollPosition.current > currentScrollPosition);
// Update ref value to new scrolling position
scrollPosition.current = currentScrollPosition;
}), []);
useEffect(() => {
window.addEventListener('scroll', handleScroll);
// Testing
console.log('Added event listener');
return () => {
window.removeEventListener('scroll', handleScroll);
// Testing
console.log('Removed event listener');
};
}, []);
// Testing
console.log('Render', scrollPosition.current);
return (
<div visible={!visible}>
<h3>Work on preventing excessive rendering </h3>
</div>
);
};
Component.propTypes = propTypes;
Component.defaultProps = defaultProps;
export default Component;