在分页查询中限制fetchMore触发的组件重渲染范围
解决Apollo Client fetchMore导致父组件重渲染的问题
核心问题原因
你遇到的情况是因为useGetUserSuspenseQuery返回的user对象在fetchMore后会被更新(缓存merge了新的预订数据),而整个User组件依赖这个user对象,所以会触发重渲染。即使提取了Bookings组件,只要还是从父组件传递user.bookings,由于user引用变化,子组件也会跟着重渲染。
具体解决方案
方案1:拆分组件+React.memo优化
把用户基础信息和预订列表拆成独立组件,用React.memo锁定不需要更新的部分:
- 提取用户信息组件
const UserInfo = React.memo(({ displayName }) => { return <Heading>{displayName}</Heading>; });
这个组件只会在displayName实际变化时重渲染,加载预订不会影响它。
- 优化预订项组件
给Booking组件也加上React.memo,确保只有当booking数据真的变化时才重渲染:
const Booking = React.memo(({ booking }) => { // 原Booking组件内容 });
这样即使User组件重渲染,旧的Booking项因为props没有变化(Apollo缓存里的对象引用是稳定的),不会重新渲染,只有新增的项会渲染。
方案2:用useFragment单独订阅预订数据
这个方法能彻底让父组件不感知预订数据的变化,是更彻底的优化:
- 定义预订数据的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 } } } `;
- 独立的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> )} </> ); }
- 改造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
相关产品推荐
相关产品推荐

