React类组件与onScroll Init的React Hook表现不一样的问题

时间:2019-04-11 11:33:34

标签: reactjs react-hooks

我正在尝试制作一个行为与我开发的React Hook相同(正确)的React类组件。当页面首次加载时,我在stackblitz中具有的示例和此处显示的示例未显示正确的图像。一旦发生滚动事件,它将变为正确。

我有以下示例显示了错误的行为。通过将import语句更改为当前已注释掉的语句,可以看到正确的行为。当我从useEffect的依赖项数组中删除isLoading时,该钩子以相同的方式破坏。

https://stackblitz.com/edit/react-scroll-problem

注意:在构造函数中将isLoading更改为false可以解决问题,但是增加了钩子所没有的图像的双重显示(首先是黑白,然后是彩色)。

import * as React from "react";

class ImageToggleOnScrollCC extends React.Component {
  constructor(props) {
    super(props);
    this.imgRef = React.createRef();
    this.state = {
      inView: false,
      isLoading: true
    };
  }

  isInView = imageRefx => {
    if (this.imgRef.current) {
      const rect = this.imgRef.current.getBoundingClientRect();
      return rect.top >= 0 && rect.bottom <= window.innerHeight;
    }
    return false;
  };

  scrollHandler = () => {
    this.setState({
      inView: this.isInView()
    });
  };

  // componentDidUpdate(prevProps) {
  //   console.log("componentDidUpdate")
  //   if (this.props.isLoading !== prevProps.isLoading) {
  //     console.log("componentDidUpdate isLoading changed")
  //   }
  // }

  componentWillUnmount() {
    window.removeEventListener("scroll", scrollHandler);
  }

  componentDidMount() {
    window.addEventListener("scroll", this.scrollHandler);
    this.setState({
      inView: this.isInView(),
      isLoading: false
    });
  }

  render() {
    if (this.state.isLoading === true) {
      return null;
    } else {
      return (
        <div>
          <i>ImageToggleOnScrollCC - Class Component</i>
          <br />
          <img
            src={
              this.state.inView
                ? 'https://via.placeholder.com/200x200.png/0000FF/808080?text=ON-SCREEN'
                : 'https://via.placeholder.com/200x200.png?text=off-screen'
            }
            alt=""
            ref={this.imgRef}
            width="200"
            height="200"
          />
        </div>
      );
    }
  }
}

export default ImageToggleOnScrollCC;

下面是正在工作的React Hook组件,我希望上面的Class组件能够正常工作。

import React, { useRef, useEffect, useState } from "react";

const ImageTogglerOnScroll = ({ primaryImg, secondaryImg }) => {
  const imageRef = useRef(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    window.addEventListener("scroll", scrollHandler);
    setInView(isInView());
    setIsLoading(false);
    return () => {
      window.removeEventListener("scroll", scrollHandler);
    };
  }, [isLoading]);

  const [inView, setInView] = useState(false);

  const isInView = () => {
    if (imageRef.current) {
      const rect = imageRef.current.getBoundingClientRect();
      return rect.top >= 0 && rect.bottom <= window.innerHeight;
    }
    return false;
  };

  const scrollHandler = () => {
    setInView(() => {
      return isInView();
    });
  };

  return isLoading ? null : (
    <div>
      <i>ImageToggleOnScroll - Functional Component React Hooks</i>
      <br />
      <img
        src={inView ? secondaryImg : primaryImg}
        alt=""
        ref={imageRef}
        width="200"
        height="200"
      />
    </div>
  );
};

export default ImageTogglerOnScroll;

1 个答案:

答案 0 :(得分:1)

这里的问题是两个组件都需要至少渲染3次才能不仅显示图像,而且显示inView时加载的正确图像。

  1. 初始渲染,它将isLoading设置为true。
  2. 只有将isLoading设置为true后,才渲染图像并分配对该元素的引用。
  3. 之后,您的组件可以追溯到img元素,确定其位置并将其标记为inView

在hook组件中,您有useEffect((), obj)来检查obj的任何更改(本例为[isLoading]),然后在hook版本中触发该组件的重新渲染。 isLoading更改并执行步骤2和3后,这将导致重新呈现。

同时,在类组件中,第三个步骤从未真正运行过(无需用户手动触发滚动事件):第一次渲染,然后在componentDidMount()中将isLoading设置为{{1 }}就这样。因为此时尚未设置图像参考,所以false返回isInView(),并且不再运行任何代码。此类组件未进行后续false更改的检查。

这是在类组件中实现该检查的一种方法:

isLoading