【问题标题】:How to use IntersectionObserver with React hooks in Gatsby如何在 Gatsby 中使用带有 React 钩子的 IntersectionObserver
【发布时间】:2023-03-10 15:50:01
【问题描述】:

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

当前的设置运行良好。我可以使用IntersectionObserver 观察ParentComponent 的孩子。

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

【问题讨论】:

    标签: reactjs react-hooks gatsby


    【解决方案1】:

    useRef 出现问题的原因很简单。您正在从孩子的useEffect 触发observeElement 函数。并且 useEffect 在 child 中运行在 useEffect 在 parent 之前,所以 observeElement 在观察者实际初始化之前运行。

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

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

    其他解决方案包括将所有子项的 ref 移至父项,并从父项本身对每个子项触发观察者,但它非常 hacky。

    【讨论】:

      猜你喜欢
      • 2020-08-06
      • 2020-12-01
      • 1970-01-01
      • 2019-10-16
      • 2021-09-29
      • 2019-04-12
      • 2020-01-02
      • 2019-08-17
      相关资源
      最近更新 更多