如何正确组合具有无限滚动逻辑的功能组件

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])