为什么该组件在状态更改时呈现两次?

时间:2019-11-21 18:36:45

标签: reactjs

我正在编码一个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);

1 个答案:

答案 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;

节气门使用https://lodash.com/docs/4.17.15#throttle

相关问题