我正在使用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>
)
}
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.
}
答案 0 :(得分:0)
useRef问题的原因很简单。您正在从孩子的observeElement
触发useEffect
功能。并且子级useEffect
在父级useEffect之前运行,因此observeElement
在观察者实际初始化之前运行。
现在,如果您不使用Gatsby,解决方案将是在useRef本身中初始化观察者。
但是,在上述情况下,最好的解决方案是将观察者实际存储在状态中并触发重新渲染,以便从子级再次调用observeElement
函数,现在实际上它将使观察者工作这是您在帖子中已经建议的内容
其他解决方案包括将所有子对象的引用移到父对象中,并在每个子对象上触发观察者从父对象本身来,但是这很棘手。