useEffect 中的 IntersectionObserver 只工作一次
IntersectionObserver inside useEffect works only once
我正在使用 React 和 Intersection Observer 构建一个无限滚动应用程序 api。
但是我卡在了 useEffect 里面 intersectionObserver 只有一次的部分。
这是代码
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>
);
}
当滚动到达底部时,scrollCallback
useEffect 中的函数起作用,它将两个数组合并为一个数组,并最终显示我想要查看的合并数据。
然而这只有效一次。 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 中的代码运行起来很奇怪
如果你能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
,你最好坚持那个参考,而不是坚持永远不会改变的东西,因为bottomRef
是 current
包装器,但它不会更改“ever”,而 passengers
状态将在相交后更改。
交替
因为 bottomRef
并不意味着改变,你也可以只观察那个节点,从不清除它的观察。
useEffect(() => {
const observer = new IntersectionObserver(scrollCallback, {
root: null,
threshold: 1,
});
observer.observe(bottomRef.current);
}, [bottomRef]);
两种方法都可以解决问题。
您需要使用 useRef
,而不是 createRef
,并且 ,您需要使用与 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
,我更喜欢显式清理,所以我可能会保留它。
我正在使用 React 和 Intersection Observer 构建一个无限滚动应用程序 api。
但是我卡在了 useEffect 里面 intersectionObserver 只有一次的部分。
这是代码
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>
);
}
当滚动到达底部时,scrollCallback
useEffect 中的函数起作用,它将两个数组合并为一个数组,并最终显示我想要查看的合并数据。
然而这只有效一次。 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 中的代码运行起来很奇怪
如果你能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
,你最好坚持那个参考,而不是坚持永远不会改变的东西,因为bottomRef
是 current
包装器,但它不会更改“ever”,而 passengers
状态将在相交后更改。
交替
因为 bottomRef
并不意味着改变,你也可以只观察那个节点,从不清除它的观察。
useEffect(() => {
const observer = new IntersectionObserver(scrollCallback, {
root: null,
threshold: 1,
});
observer.observe(bottomRef.current);
}, [bottomRef]);
两种方法都可以解决问题。
您需要使用 useRef
,而不是 createRef
,并且 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
,我更喜欢显式清理,所以我可能会保留它。