Проблема с компонентом класса React, который не ведет себя так же, как React Hook с onScroll Init - PullRequest
1 голос
/ 11 апреля 2019

Я пытаюсь создать компонент класса React, который ведет себя так же (и правильно), как и React Hook, который я разработал. Пример, который я привел в stackblitz и показан здесь, не показывает правильные изображения при первой загрузке страницы. Когда происходит событие прокрутки, оно становится правильным.

У меня есть следующий пример, который показывает неправильное поведение. Вы можете увидеть правильное поведение, изменив оператор импорта на тот, который в настоящее время закомментирован. Хук прерывается таким же образом, когда я удаляю isLoading из массива зависимостей useEffect.

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 Ответ

1 голос
/ 11 апреля 2019

Проблема здесь заключается в том, что оба компонента должны отображаться как минимум три раза, чтобы показать не только изображение, но и правильное изображение, загруженное при inView.

  1. Первоначальный рендер, который установит isLoading в true.
  2. Только после того, как isLoading было установлено в true, это происходит, когда изображение визуализируется, и ссылка на элемент будет назначена.
  3. После этого ваш компонент может вернуться к элементу img, определить его положение и отметить его как inView или нет.

В компоненте ловушки у вас есть useEffect((), obj), который проверяет любое изменение obj (в этом случае [isLoading]), а затем запускает повторную визуализацию для компонента в версии ловушки. Это приводит к повторному рендерингу после изменения isLoading и выполнению шагов 2 и 3.

Между тем, в компоненте класса 3-й шаг фактически никогда не выполняется (без запуска пользователем события прокрутки вручную): он рендерится в первый раз, затем в componentDidMount() устанавливает isLoading в false и все. Поскольку в этот момент ссылка на изображение еще не была установлена, isInView() возвращает false и больше не выполняется код. В этом компоненте класса отсутствует проверка на последующие isLoading изменения.

Вот способ реализовать эту проверку в компоненте класса:

class ImageToggleOnScrollCC extends React.Component {
  // ...

  componentDidUpdate(prevProps, prevState) {
    if (this.state.isLoading !== prevState.isLoading) {
      console.log("componentDidUpdate isLoading changed")
      console.log("this.imgRef.current is also assigned at this time", this.imgRef.current)

      this.setState({
        inView: this.isInView(), // will return true and rerender
      });
    }
  }

  // ...
}
...