【问题标题】:Why callbacks in react functional component is not reading the updated state value为什么反应功能组件中的回调没有读取更新的状态值
【发布时间】:2020-01-04 10:27:17
【问题描述】:

我正在尝试在 react 中使用交叉点观察器来实现无限滚动,但我面临的问题是,在交叉点观察器的回调中,我无法读取当前“页面”和“列表”的最新值,所以我可以获取下一页的数据。

import ReactDOM from "react-dom";
import "./styles.css";
require("intersection-observer");

const pageSize = 30;
const threshold = 5;

const generateList = (page, size) => {
  let arr = [];
  for (let i = 1; i <= size; i++) {
    arr.push(`${(page - 1) * size + i}`);
  }

  return arr;
};

const fetchList = page => {
  return new Promise(resolve => {
    setTimeout(() => {
      return resolve(generateList(page, pageSize));
    }, 1000);
  });
};

let options = {
  root: null,
  threshold: 0
};

function App() {
  const [page, setPage] = useState(1);
  const [fetching, setFetching] = useState(false);
  const [list, setlist] = useState(generateList(page, pageSize));

  const callback = entries => {
    if (entries[0].isIntersecting) {
      observerRef.current.unobserve(
        document.getElementById(`item_${list.length - threshold}`)
      );
      setFetching(true);
/* at this point neither the 'page' is latest nor the 'list'
*they both have the initial states.
*/
      fetchList(page + 1).then(res => {
        setFetching(false);
        setPage(page + 1);
        setlist([...list, ...res]);
      });
    }
  };

  const observerRef = useRef(new IntersectionObserver(callback, options));

  useEffect(() => {
    if (observerRef.current) {
      observerRef.current.observe(
        document.getElementById(`item_${list.length - threshold}`)
      );
    }
  }, [list]);

  return (
    <div className="App">
      {list.map(l => (
        <p key={l} id={`item_${l}`}>
          {l}
        </p>
      ))}
      {fetching && <p>loading...</p>}
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

当前行为: 'page' 和 'list' 的值始终等于初始状态,而不是最新值。第 2 页后无限滚动不起作用

预期行为:在回调函数中,它应该读取状态“页面”和“列表”的更新值。

这是这个demo的工作沙箱https://codesandbox.io/s/sweet-sun-rbcml?fontsize=14&hidenavigation=1&theme=dark

【问题讨论】:

  • 而不是observerRef.current = new IntersectionObserver(callback, options);observerRef.current = new IntersectionObserver((entries) =&gt; callback(entries, page, fetching, list), options); 并相应地更新callback 方法签名怎么样? (设置方法很好,只有值作为参数包含)
  • 没什么区别

标签: javascript reactjs react-hooks


【解决方案1】:

这里主要有两个问题,闭包和直接查询 DOM。

要解决闭包问题,请使用函数式useState 和引用:

const listLengthRef = useRef(list.length);
const pageRef = useRef(page);

const callback = useCallback(entries => {
  if (entries[0].isIntersecting) {
    observerRef.current.unobserve(
      document.getElementById(`item_${listLengthRef.current - threshold}`)
    );
    setFetching(true);
    fetchList(pageRef.current + 1).then(res => {
      setFetching(false);
      setPage(page => page + 1);
      setlist(list => [...list, ...res]);
    });
  }
}, []);

const observerRef = useRef(new IntersectionObserver(callback, options));

useEffect(() => {
  listLengthRef.current = list.length;
}, [list]);

useEffect(() => {
  pageRef.current = page;
}, [page]);

虽然此代码有效,但您应该将document.getElementById 替换为引用,在这种情况下,它将是对页面最后一个元素的引用。

【讨论】:

    【解决方案2】:

    您可以使用 React setState callback method 来保证您将收到以前的值。

    更新您的callback 函数如下,它应该可以工作。

    const callback = entries => {
      if (entries[0].isIntersecting) {
        setFetching(true);
        setPage(prevPage => {
          fetchList(prevPage + 1).then(res => {
            setFetching(false);
            setlist(prevList => {
              observerRef.current.unobserve(document.getElementById(`item_${prevList.length - threshold}`));
              return ([...prevList, ...res]);
            });
          })
          return prevPage + 1;
        })
      }
    };
    

    【讨论】:

      【解决方案3】:

      我认为问题是由于 ref 一直引用旧的观察者。每次更新依赖项时都需要刷新观察者。它与js中的闭包有关。我会更新您的应用以将回调移动到 useEffect 中

      function App() {
        const [page, setPage] = useState(1);
        const [fetching, setFetching] = useState(false);
        const [list, setlist] = useState(generateList(page, pageSize));
      
      
        const observerRef = useRef(null);
      
        useEffect(() => {
          const callback = entries => {
            if (entries[0].isIntersecting) {
              observerRef.current.unobserve(
                document.getElementById(`item_${list.length - threshold}`)
              );
              setFetching(true);
          /* at this point neither the 'page' is latest nor the 'list'
           *they both have the initial states.
           */
              console.log(page, list);
              fetchList(page + 1).then(res => {
                setFetching(false);
                setPage(page + 1);
                setlist([...list, ...res]);
              });
            }
          };
          observerRef.current = new IntersectionObserver(callback, options);
      
          if (observerRef.current) {
            observerRef.current.observe(
              document.getElementById(`item_${list.length - threshold}`)
            );    
          }
        }, [list]);
      
        return (
          <div className="App">
            {list.map(l => (
              <p key={l} id={`item_${l}`}>
                {l}
              </p>
            ))}
            {fetching && <p>loading...</p>}
          </div>
        );
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-07-21
        • 2022-06-27
        • 1970-01-01
        • 2021-07-19
        • 2021-01-24
        • 2017-05-23
        • 2022-01-21
        • 2022-10-06
        相关资源
        最近更新 更多