useEffect中的 IntersectionObserver只工作一次



我正在使用react和intersection observer api构建一个无限滚动应用程序。

但是我被卡在了useEffect中只有一次交集的部分。

代码

import React, { useState, useEffect, createRef } from 'react';
import axios from 'axios';
function App() {
const [page, setPage] = useState(1);
const [passengers, setPassengers] = useState([]);
const bottomRef = createRef();
const scrollCallback = (entries) => {
if (entries[0].isIntersecting) {
axios.get(`https://api.instantwebtools.net/v1/passenger?page=${page}&size=10`).then(res => setPassengers(prevState => [...prevState, ...res.data.data]));
}
}; 

useEffect(() => {
const observer = new IntersectionObserver(scrollCallback, {
root: null,
threshold: 1,
});
observer.observe(bottomRef.current);
return () => {
observer.disconnect();
}
}, [bottomRef]);
return (
<div className="container">
<div className="lists">
{
passengers.map((passenger, idx) => {
const { airline, name, trips } = passenger;
return (
<div className="list" key={idx}>
<h4>User Info</h4>
<p>name: {name}</p>
<p>trips: {trips}</p>
<h4>Airline Info</h4>
<p>name: {airline.name}</p>
<p>country: {airline.country}</p>
<p>established: {airline.established}</p>
<p>head quarter: {airline.head_quarters}</p>
<p>website: {airline.website}</p>
</div>
)
})
}
</div>
<div ref={bottomRef} />
</div>
);
}

当滚动到达底部时,useEffect中的scrollCallback函数将两个数组合并为一个,并最终显示我想要看到的合并数据。

然而,这只工作一次。useEffect生命周期不再工作。所以我把observer.disconnect();改成了observer.unobserve(bottomRef.current);但是出现了一个错误,说

TypeError: Failed to execute 'unobserve' on 'IntersectionObserver': parameter 1 is not of type 'Element'.

为什么useEffect生命周期不能读取bottomRef??

工作代码示例:https://codesandbox.io/s/dazzling-newton-jvivj?file=/src/App.js

codesandbox里的代码居然还能用

您需要使用useRef,而不是createRef,并且正如Andrea所说,您需要使用与observe相同的unobserve元素。最后,因为你的useEffect回调使用的是bottomRef.current,这是在你的依赖项数组中使用的依赖项。

下面是一个工作示例,参见***注释:

const { useState, useEffect, useRef } = React;
function App() {
const [page, setPage] = useState(1);
const [passengers, setPassengers] = useState([]);
const bottomRef = useRef(null); // *** Not `createRef`
const scrollCallback = (entries) => {
if (entries[0].isIntersecting) {
getMorePassengers()
.then(res => setPassengers(prevState => [...prevState, ...res.data.data]));
// *** Should handle/report errors here
}
};
useEffect(() => {
// *** Grab the element related to this callback
const { current } = bottomRef;
const observer = new IntersectionObserver(scrollCallback, {
root: null,
threshold: 1,
});
observer.observe(current);
return () => {
observer.disconnect(current); // *** Use the same element
}
}, [bottomRef.current]); // *** Note dependency
return (
<div className="container">
<div className="lists">
{
passengers.map((passenger, idx) => {
// *** I simplified this for the example
const { name } = passenger;
return (
<div className="list" key={idx}>
<p>name: {name}</p>
</div>
)
})
}
</div>
<div ref={bottomRef} />
</div>
);
}
// ** Stand-in for ajax
let passengerCounter = 0;
function getMorePassengers() {
return new Promise(resolve => {
resolve({
data: {
data: [
// Obviously, you don't use things like `passengerCounte`
{name: `Passenger ${passengerCounter++}`},
{name: `Passenger ${passengerCounter++}`},
{name: `Passenger ${passengerCounter++}`},
{name: `Passenger ${passengerCounter++}`},
{name: `Passenger ${passengerCounter++}`},
]
}
});
});
}
ReactDOM.render(<App/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>

但是你可能根本不需要那个清理回调。Andrea Giammarchi说IntersectionObserver使用弱引用被观察到的元素,A)他几乎总是对的,所以我相信他;B)这样设计是有道理的。所以你可以完全放弃清理回调。

单独地,我希望您正在观察的div只会在组件挂载时创建一次,并且之后永远不会重新创建。所以理论上你可以只使用[]作为你的依赖数组。

也就是说,如果React有理由重新创建div,我会学习保留依赖项,我更喜欢显式清理,所以我可能会离开它。

如果你可以observe引用,你当然也可以unobserve,只要你有正确的,相同的引用。

useEffect(() => {
const observer = new IntersectionObserver(scrollCallback, {
root: null,
threshold: 1,
});
const {current} = bottomRef;
observer.observe(current);
return () => {
observer.unobserve(current);
};
}, [passengers]);

这不会说current不是一个节点。

此外>,如果改变的是passengers,你最好保持那个引用,而不是保持一些永远不会改变的东西,因为bottomRefcurrent的包装器,但它永远不会改变,而passengers状态一旦相交就会改变。

可以选择

由于bottomRef不打算更改,您也可以只观察该节点而不清除其观察值。

useEffect(() => {
const observer = new IntersectionObserver(scrollCallback, {
root: null,
threshold: 1,
});
observer.observe(bottomRef.current);
}, [bottomRef]);

两种方法都能解决问题。