如何在Gatsby中将IntersectionObserver与React挂钩一起使用

时间:2020-04-27 20:22:18

标签: reactjs react-hooks gatsby

Example on 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的孩子们。

但是,我需要将此组件转换为功能组件。我尝试使用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 answer,由于Gatsby使用服务器端渲染,因此IntersectionObserver在Gatsby的构建过程中不可用。
const ParentComponent = () => {
  const observer = useRef(new IntersectionObserver())
  // Throws an error: IntersectionObserver is not defined.
}

1 个答案:

答案 0 :(得分:0)

useRef问题的原因很简单。您正在从孩子的observeElement触发useEffect功能。并且子级useEffect在父级useEffect之前运行,因此observeElement在观察者实际初始化之前运行。

现在,如果您不使用Gatsby,解决方案将是在useRef本身中初始化观察者。

但是,在上述情况下,最好的解决方案是将观察者实际存储在状态中并触发重新渲染,以便从子级再次调用observeElement函数,现在实际上它将使观察者工作这是您在帖子中已经建议的内容

其他解决方案包括将所有子对象的引用移到父对象中,并在每个子对象上触发观察者从父对象本身来,但是这很棘手。