首页 > 解决方案 > 为什么这个组件在状态改变时会渲染两次?

问题描述

我正在编写一个ReactHeader 组件,该组件将在滚动时滑出。(代码在底部)

现在,这个组件的渲染次数超出了我的预期。我在组件中的每个“操作”中添加了一个 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);

标签: reactjs

解决方案


我认为因为 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;

油门使用情况,https://lodash.com/docs/4.17.15#throttle


推荐阅读