如何正确组合具有无限滚动逻辑的功能组件
How to properly compose functional components that have infinite scrolling logic
我正在将 class 组件转换为功能组件以供练习。它有一个 ref 对象来包含组件的一些变量,例如 IntersectionObserver 对象来实现无限滚动。
问题从这里开始。 IntersectionObserver 的回调函数调用组件中定义的函数(如更新)来加载更多数据。因为IntersectionObserver是在useRef内部定义的,所以update函数是组件初始化时绑定的函数。所以更新函数中使用的状态值也是初始状态的值。
如何以正确的方式组合此功能组件?
Backbone 演示
export default function A(props) {
const [state, setState] = useState({
pageNo: 1,
isLoading: false,
items: []
});
const update = useCallback(() => {
setState(state => ({...state, isLoading: true}));
someApi(state.pageNo);
setState(state => ({
...state,
pageNo: pageNo + 1
}));
setState(state => ({...state, isLoading: false}));
}, [isLoading, pageNo]);
const observerCallback = useCallback((entries, observer) => {
for (const entry of entries) {
if (entry.isIntersecting) {
observer.disconnect();
update();
}
}
}, [update]);
const observer = useRef(new IntersectionObserver(observerCallback)); // The callback is the function binding the update function that binds some of the initial state
const lastEl = useRef(null);
const preLastEl = useRef(null);
useEffect(() => {
update();
}, [props]);
if (lastEl.current && lastEl.current != preLastEl.current) {
preLastEl.current = lastEl.current;
observer.observe(lastEl.current);
}
return (
<SomeProgressBar style={{ display: state.isLoading ? "" : "none" }}/>
{
state.items.map((item) => <B ... ref={lastEl}/>)
}
);
}
我不明白您为什么要使用 ref
以及为什么您不能以不同的方式使用它。所以如果你必须这样做,你的 refs
依赖于 state
对象,当状态改变时它们需要改变,所以你应该使用 useEffect
来改变refs
基于新 state
。尝试执行以下两个步骤之一:
1
const refs = useRef({
lastEl: undefined,
observer: new IntersectionObserver((entries, observer) => {
...
update(state.pageNo); // This is the update function bound when the instance of this component gets initialized
});
});
useEffect(() => {
update(state.pageNo);
}, [props]);
function update(pageNo = 1) {
setState(prev => ({...prev, isLoading: true}));
someApi(pageNo); // state.pageNo will be always 1
setState(prev => ({...prev, isLoading: false}));
}
2
如果上面的代码不起作用试试这个
useEffect(() => {
if(state.pageNo){
refs.current = {
lastEl: undefined,
observer: new IntersectionObserver((entries, observer) => {
...
update(state.pageNo); // This is the update function bound when the instance of this component gets initialized
});
}
}
}, [state.pageNo])
我正在将 class 组件转换为功能组件以供练习。它有一个 ref 对象来包含组件的一些变量,例如 IntersectionObserver 对象来实现无限滚动。
问题从这里开始。 IntersectionObserver 的回调函数调用组件中定义的函数(如更新)来加载更多数据。因为IntersectionObserver是在useRef内部定义的,所以update函数是组件初始化时绑定的函数。所以更新函数中使用的状态值也是初始状态的值。
如何以正确的方式组合此功能组件?
Backbone 演示
export default function A(props) {
const [state, setState] = useState({
pageNo: 1,
isLoading: false,
items: []
});
const update = useCallback(() => {
setState(state => ({...state, isLoading: true}));
someApi(state.pageNo);
setState(state => ({
...state,
pageNo: pageNo + 1
}));
setState(state => ({...state, isLoading: false}));
}, [isLoading, pageNo]);
const observerCallback = useCallback((entries, observer) => {
for (const entry of entries) {
if (entry.isIntersecting) {
observer.disconnect();
update();
}
}
}, [update]);
const observer = useRef(new IntersectionObserver(observerCallback)); // The callback is the function binding the update function that binds some of the initial state
const lastEl = useRef(null);
const preLastEl = useRef(null);
useEffect(() => {
update();
}, [props]);
if (lastEl.current && lastEl.current != preLastEl.current) {
preLastEl.current = lastEl.current;
observer.observe(lastEl.current);
}
return (
<SomeProgressBar style={{ display: state.isLoading ? "" : "none" }}/>
{
state.items.map((item) => <B ... ref={lastEl}/>)
}
);
}
我不明白您为什么要使用 ref
以及为什么您不能以不同的方式使用它。所以如果你必须这样做,你的 refs
依赖于 state
对象,当状态改变时它们需要改变,所以你应该使用 useEffect
来改变refs
基于新 state
。尝试执行以下两个步骤之一:
1
const refs = useRef({
lastEl: undefined,
observer: new IntersectionObserver((entries, observer) => {
...
update(state.pageNo); // This is the update function bound when the instance of this component gets initialized
});
});
useEffect(() => {
update(state.pageNo);
}, [props]);
function update(pageNo = 1) {
setState(prev => ({...prev, isLoading: true}));
someApi(pageNo); // state.pageNo will be always 1
setState(prev => ({...prev, isLoading: false}));
}
2
如果上面的代码不起作用试试这个
useEffect(() => {
if(state.pageNo){
refs.current = {
lastEl: undefined,
observer: new IntersectionObserver((entries, observer) => {
...
update(state.pageNo); // This is the update function bound when the instance of this component gets initialized
});
}
}
}, [state.pageNo])