You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在分页查询中限制fetchMore触发的组件重渲染范围

解决Apollo Client fetchMore导致父组件重渲染的问题

核心问题原因

你遇到的情况是因为useGetUserSuspenseQuery返回的user对象在fetchMore后会被更新(缓存merge了新的预订数据),而整个User组件依赖这个user对象,所以会触发重渲染。即使提取了Bookings组件,只要还是从父组件传递user.bookings,由于user引用变化,子组件也会跟着重渲染。

具体解决方案

方案1:拆分组件+React.memo优化

把用户基础信息和预订列表拆成独立组件,用React.memo锁定不需要更新的部分:

  1. 提取用户信息组件
const UserInfo = React.memo(({ displayName }) => {
  return <Heading>{displayName}</Heading>;
});

这个组件只会在displayName实际变化时重渲染,加载预订不会影响它。

  1. 优化预订项组件
    给Booking组件也加上React.memo,确保只有当booking数据真的变化时才重渲染:
const Booking = React.memo(({ booking }) => {
  // 原Booking组件内容
});

这样即使User组件重渲染,旧的Booking项因为props没有变化(Apollo缓存里的对象引用是稳定的),不会重新渲染,只有新增的项会渲染。

方案2:用useFragment单独订阅预订数据

这个方法能彻底让父组件不感知预订数据的变化,是更彻底的优化:

  1. 定义预订数据的Fragment
import { gql, useFragment } from '@apollo/client';

const USER_BOOKINGS_FRAGMENT = gql`
  fragment UserBookings on User {
    bookings(first: $first, after: $after) {
      nodes {
        id
        # 你的其他预订字段
      }
      pageInfo {
        endCursor
        hasNextPage
      }
    }
  }
`;
  1. 独立的Bookings组件
    让Bookings组件直接从缓存订阅数据,不再依赖父组件传递:
function Bookings({ userId, number }) {
  const { data, fetchMore } = useFragment({
    fragment: USER_BOOKINGS_FRAGMENT,
    fragmentName: 'UserBookings',
    variables: {
      first: 10, // 初始加载条数
      after: null
    },
    id: `User:${userId}` // 缓存中用户的唯一标识,格式要和Apollo缓存的key一致
  });

  const fetchMoreBookings = async () => {
    await fetchMore({
      variables: {
        number,
        after: data.bookings.pageInfo.endCursor
      }
    });
  };

  return (
    <>
      <ul>
        {data.bookings.nodes.map(booking =>
          <Booking key={booking.id} booking={booking} />
        )}
      </ul>
      {data.bookings.pageInfo.hasNextPage && (
        <Link onClick={fetchMoreBookings}>load older</Link>
      )}
    </>
  );
}
  1. 改造User组件
    现在User组件只负责获取用户基础信息,完全不碰预订数据:
function User() {
  const { number } = useParams();

  const { data: { user } } = useGetUserSuspenseQuery({
    variables: { number }
  });

  return (
    <>
      <UserInfo displayName={user.displayName} />
      <Bookings userId={user.id} number={number} />
    </>
  );
}

这样调用fetchMore时,只有Bookings组件会因为缓存更新重渲染,User和UserInfo组件完全不受影响。

关于嵌套连接场景的疑问

这种不必要的重渲染完全可以避免,核心是不要把不相关的数据放在同一个查询里,也不要让父组件依赖不需要更新的子数据。通过拆分数据订阅范围,配合React的memo优化,就能精准控制重渲染的组件。


内容的提问来源于stack exchange,提问作者Tim Scott

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 06:13:16