Как использовать IntersectionObserver с перехватчиками React в Gatsby - PullRequest
0 голосов
/ 27 апреля 2020

Пример на CodeSandbox


Я создаю веб-сайт с использованием Gatsby, и у меня возникают проблемы с преобразованием компонента класса, который использует IntersectionObserver, в функциональный компонент, который использует хуки , Я особенно ищу решение, которое минимизирует количество повторных рендеров, насколько это возможно.

Мой компонент класса показан ниже:

Родительский компонент с использованием React.Component

import React, { Component } from "react"
import ChildComponent from "../components/child"

class ParentComponent extends Component {
  observer = null

  componentDidMount() {
    this.observer = new IntersectionObserver(this.handleObserverEvent, {})
  }

  componentWillUnmount() {
    this.observer.disconnect()
    this.observer = null
  }

  handleObserverEvent = entries => {
    entries.forEach(entry => console.log(entry))
  }

  observeElement = ref => {
    if (this.observer) this.observer.observe(ref)
  }

  render() {
    const colors = ["red", "orange", "yellow", "green", "blue"]

    return (
      <div>
        {colors.map(color => (
          <ChildComponent
            key={color}
            color={color}
            observeElement={this.observeElement}
          />
        ))}
      </div>
    )
  }
}

export default ParentComponent

Дочерний компонент

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

const ChildComponent = ({ color, observeElement }) => {
  const ref = useRef()

  useEffect(() => {
    if (ref.current) observeElement(ref.current)
  }, [observeElement])

  return (
    <div
      className={color}
      ref={ref}
      style={{
        width: "100vw",
        height: "100vh",
        background: color
      }}
    >
      {color}
    </div>
  )
}

export default ChildComponent


Эта текущая настройка работает хорошо. Я могу наблюдать за детьми ParentComponent, используя IntersectionObserver.

Однако мне нужно преобразовать этот компонент в функциональный компонент. Я пытался реализовать эту же логику c, используя useRef, но не смог достичь того же результата. В observeElement, observer.current равно null. Мой функциональный компонент показан ниже.

Родительский компонент, использующий useRef

// Omitted imports/exports

const handleObserverEvent = entries => {
  entries.forEach(entry => console.log(entry))
}

const ParentComponent = () => {
  const observer = useRef(null)

  useEffect(() => {
    observer.current = new IntersectionObserver(handleObserverEvent, {})

    return () => {
      if (observer.current) observer.current.disconnect()
      observer.current = null
    }
  }, [])

  const observeElement = ref => {
    console.log(observer.current) // Logs null
    if (observer.current) observer.current.observe(ref)
  }

  const colors = ["red", "orange", "yellow", "green", "blue"]

  return (
    <div>
      {colors.map(color => (
        <ChildComponent
          key={color}
          color={color}
          observeElement={observeElement}
        />
      ))}
    </div>
  )
}


Для борьбы с этим я выбрал используйте useState для запуска повторного рендеринга, который работает. Однако мне интересно, можно ли добиться того же с помощью useRef и, возможно, некоторых других хуков.

Родительский компонент с использованием useState

// Omitted imports/exports

const handleObserverEvent = entries => {
  // This successfully logs the entries
  entries.forEach(entry => console.log(entry))
}

const ParentComponent = () => {
  const [observer, setObserver] = useState(null)

  useEffect(() => {
    setObserver(new IntersectionObserver(handleObserverEvent, {}))

    return () => {
      if (observer) observer.disconnect()
    }
  }, [])

  const observeElement = ref => {
    if (observer) observer.observe(ref)
  }

  const colors = ["red", "orange", "yellow", "green", "blue"]

  return (
    <div>
      {colors.map(color => (
        <ChildComponent
          key={color}
          color={color}
          observeElement={observeElement}
        />
      ))}
    </div>
  )
}

Примечания

  1. Я не могу установить new IntersectionObserver() в качестве начального значения в useRef. Это потому, что он выдает ошибку при запуске gatsby build. Согласно этому ответу Stack Overflow , IntersectionObserver недоступен во время процесса сборки Gatsby, поскольку Gatsby использует рендеринг на стороне сервера.
const ParentComponent = () => {
  const observer = useRef(new IntersectionObserver())
  // Throws an error: IntersectionObserver is not defined.
}

1 Ответ

0 голосов
/ 01 мая 2020

Причина вашей проблемы с useRef довольно проста. Вы запускаете функцию observeElement от ребенка useEffect. и useEffect в потомке выполняется до использования useEffect в родительском элементе, поэтому observeElement выполняется до того, как наблюдатель фактически инициализируется.

Теперь, если бы вы не использовали Gatsby, решение было бы инициализировать наблюдателя внутри самого useRef.

Однако лучшее решение в приведенном выше случае - это фактически сохранить наблюдателя в состоянии и запустить повторную визуализацию, чтобы функция observeElement снова вызывалась у дочернего элемента, и теперь у нее фактически будет наблюдатель для работа, которую вы уже предложили в своем посте

Другие решения включают в себя перемещение ссылки всех детей в родителя и запуск наблюдателя от самого родителя для каждого ребенка, но он довольно хакерский.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...