对比新旧对象数组更新状态,Apollo增删后重查引发全量渲染问题
嗨,这个问题我之前在项目里也踩过坑!用RefetchQueries确实会触发全量数据拉取,导致整个列表的所有项都重新渲染——明明只是新增了一条数据,却要把旧的都重新渲染一遍,实在没必要。其实Apollo Client提供了更精准的缓存更新方式,能完美解决这个问题,下面给你一步步讲怎么优化:
1. 用Mutation的update函数手动更新缓存(核心方案)
RefetchQueries的本质是重新发起Query请求,替换整个缓存数据,所以React会认为整个列表数组变了,进而重渲染所有项。而用update函数,我们可以直接在缓存里修改数据,不用发起新的网络请求,而且只更新需要变化的部分。
举个实际的代码例子:
首先定义你的Query和Mutation:
# 获取名称列表的Query const GET_NAMES = gql` query GetNames { names { id name } } `; # 添加名称的Mutation const ADD_NAME = gql` mutation AddName($name: String!) { addName(name: $name) { id name } } `;
然后在使用useMutation的时候,加上update函数:
const [addName] = useMutation(ADD_NAME, { update(cache, { data: { addName: newName } }) { // 1. 先从缓存里读取现有的名称列表 const { names } = cache.readQuery({ query: GET_NAMES }); // 2. 把新添加的项合并到旧列表里(注意要返回新数组,保持immutable) cache.writeQuery({ query: GET_NAMES, data: { names: [...names, newName] }, }); }, // 这里直接删掉RefetchQueries,不需要再全量拉取了! // refetchQueries: [{ query: GET_NAMES }], });
这样操作后,Apollo会直接更新缓存中的列表,组件会感知到缓存变化,但只有新增的项对应的组件会重新渲染——因为旧项的对象引用没有变。
2. 给列表项组件加上React.memo(确保旧项不重渲染)
光更新缓存还不够,如果你列表项组件没有做优化,React可能还是会重渲染所有项。这时候需要用React.memo包裹列表项组件,让它只在props真正变化的时候才重新渲染:
import React from 'react'; // 用React.memo包裹,组件会浅比较props,只有变化时才渲染 const NameItem = React.memo(({ name, id }) => { console.log(`渲染:${name}`); // 测试用,新增时只有新名字会打印 return <div key={id}>{name}</div>; });
如果是类组件的话,可以继承PureComponent,效果是一样的。
3. 确认Query的fetchPolicy(可选,默认已满足)
确保你的useQuery使用的是默认的fetchPolicy: 'cache-first',这样组件会优先从缓存读取数据,不会在缓存更新后又发起网络请求。如果之前改过这个配置,记得改回来:
const { data } = useQuery(GET_NAMES, { fetchPolicy: 'cache-first', // 这是默认值,可写可不写 });
额外说明:删除操作的类似优化
如果是删除名称的场景,同样可以用update函数来处理,只需要过滤掉要删除的项:
const [deleteName] = useMutation(DELETE_NAME, { update(cache, { data: { deleteName: deletedId } }) { const { names } = cache.readQuery({ query: GET_NAMES }); cache.writeQuery({ query: GET_NAMES, data: { names: names.filter(item => item.id !== deletedId) }, }); }, });
这样调整之后,不管是新增还是删除,都只会影响对应的列表项,旧的项完全不会重新渲染,性能一下就上来了!
内容的提问来源于stack exchange,提问作者Vialito

