reactjs - 为什么这个组件在状态改变时会渲染两次?
问题描述
我正在编写一个React
Header 组件,该组件将在滚动时滑出。(代码在底部)
useEffect
我在钩子中分配了一个事件处理程序,它会handleChange
在发生滚动事件时调用该函数。我已经将此handleChange
函数包装在一个useCallback
钩子中以提高性能(无需在每次渲染时重新实例化)。我也用
React.memo
.我将滚动位置存储在 a 中
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);
解决方案
我认为因为 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;
推荐阅读
- php - 用php显示随机方程的结果
- regex - 在正则表达式领域,什么是回溯?
- java - 如何在java中读取.csv中文文件以及为什么这两个导致不同的结果
- sparql - 使用 GROUP_CONCAT 和 BIND 生成单个语句
- mongodb - 如何改进 MongoDB 中的地理空间查询
- javascript - 状态突变是副作用?
- tensorflow - 从 Java 中使用 DNNRegressor 生成的模型中读取权重
- c - socket() 中“type”为 SOCK_DGRAM 或 SOCK_STREAM 时是否需要指定“protocol”?
- django - Google OAuth 2.0 不断尝试使用“http”url 进行回调
- pandas - 比较特定列的值并将值添加到另一个 | 熊猫 | Python