You need to enable JavaScript to run this app. React滚动到底部实现分页加载时父组件状态不更新问题求解
优惠活动
大模型
产品
解决方案
定价
更多

React滚动到底部实现分页加载时父组件状态不更新问题求解

问题根因
  • 错误的Firebase监听嵌套:useEffect中挂载的.onSnapshot(fetchData)将fetchData作为快照回调,但fetchData本身不接收快照参数,还会在内部重复创建新的监听。每触发一次集合变更或滚动加载,就会多生成一个重复无效的监听,所有监听都执行相同的第一页查询。
  • 闭包陷阱:fetchData的useCallback使用空依赖数组,函数从首次渲染创建后就不会更新,内部引用的lastVisible永远是初始值0,分页查询的startAfter参数永远取初始值,根本不会拉取后续分页数据。
  • 状态更新逻辑错误:拿到分页数据后直接执行setfollowingData(res),完全覆盖已加载的列表数据,没有追加逻辑。就算查询到新数据,也会把旧数据清空。
  • 监听未清理:fetchData内部创建的.onSnapshot没有存储返回的清理句柄,每次调用都会残留监听,造成内存泄漏,还会多次触发状态更新。

之前触发保存操作后能加载数据的原因:保存操作会触发Firebase集合变更,激活原有嵌套的监听推送,拿到的数据走到setfollowingData后触发重渲染,积累的分页数据才会显示出来。

修正实现

父组件优化

  1. 调整useCallback依赖项,保证fetchData能读取最新的lastVisible状态
  2. 用useRef做请求锁,防止滚动重复触发
  3. 状态更新时追加新分页数据到原有列表,不覆盖旧数据
  4. 初始状态设为空数组,避免FlatList收到undefined报错
  5. 分页查询使用单次get方法,避免重复监听
function Following({route, navigation}) {
  const [followingData, setfollowingData] = useState([]);
  const [loading, setLoading] = useState(true);
  const [lastVisible, setLastVisible] = useState(null);
  const isFetching = useRef(false);

  const fetchData = useCallback(async () => {
    if (isFetching.current) return;
    isFetching.current = true;
    setLoading(true);
    try {
      const dataRef = firestore().collection('usernames');
      let query = dataRef
        .doc(route.params.username.toLowerCase())
        .collection('Following')
        .orderBy('followedAt')
        .limit(7);
      
      // 存在分页游标时才做偏移
      if (lastVisible) {
        query = query.startAfter(lastVisible);
      }

      const snapshot = await query.get();
      if (snapshot.empty) return;

      // 更新分页游标为当前页最后一条记录
      const lastDoc = snapshot.docs[snapshot.docs.length - 1];
      setLastVisible(lastDoc.data().followedAt);

      // 批量拉取用户详情
      const userList = await Promise.all(
        snapshot.docs.map(async (doc) => {
          const followData = doc.data();
          const userRes = await followData.path.get();
          const userData = userRes.data();
          return {
            profileName: doc.id,
            displayName: userData.displayName ?? null,
            followerCount: userData.followers ?? 0,
            followingCount: userData.following ?? 0,
            imageUrl: userData.imageUrl ?? null,
          };
        })
      );

      // 追加新数据,而非覆盖
      setfollowingData(prev => [...prev, ...userList]);
    } catch (err) {
      console.log('分页加载失败', err);
    } finally {
      isFetching.current = false;
      setLoading(false);
    }
  }, [lastVisible, route.params.username]);

  useEffect(() => {
    // 路由参数变化时重置列表状态,重新拉取第一页
    setfollowingData([]);
    setLastVisible(null);
    fetchData();
  }, [fetchData]);

  return (
    <>
      {loading && followingData.length === 0 ? (
        <ActivityIndicator size="large" color="black" />
      ) : (
        <FollowersScreens
          data={followingData}
          screen={'Following'}
          username={route.params.username}
          navigation={navigation}
          fetchData={fetchData}
          loading={loading}
        />
      )}
    </>
  );
}

子组件优化

  1. 调整onEndReachedThreshold为0.5,距离列表底部50%时提前触发加载,优化体验
  2. 用列表项的唯一值profileName作为key,替代不稳定的index作为key
  3. 增加底部加载提示,避免重复请求时无反馈
function FollowersScreens({
  data,
  username,
  navigation,
  fetchData,
  loading
}) {
  return (
    <FlatList
      scrollEnabled={true}
      onEndReachedThreshold={0.5}
      onEndReached={fetchData}
      data={data}
      keyExtractor={(item) => item.profileName}
      ListFooterComponent={loading ? <ActivityIndicator style={{margin:16}} /> : null}
      renderItem={({item}) => {
        return (
          // 保留原有列表项渲染逻辑
          <>{/* 你的原有页面渲染代码 */}</>
        );
      }}
    />
  );
}

内容的提问来源于stack exchange,提问作者Junaid Razaq

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.06 19:45:06