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

Apollo Client乐观响应仅生效一次问题排查求助

Apollo乐观响应后续突变无法即时更新UI问题排查与解决

问题场景

使用Apollo的**乐观响应(optimistic response)**结合cache.modify()实现列表即时添加新对象的UI效果,首次操作可正常即时更新,但后续每次突变都无法即时刷新UI(需等待3~5秒)。控制台显示缓存已即时更新,但UI未同步,仅首次突变有效。

数据读取代码

const { status } = useSession();
const { data: { deliveries } = { deliveries: null } } = useQuery(
    GET_DELIVERIES,
    {
        skip: status !== "authenticated",
        fetchPolicy: "cache-and-network"
    }
);

useEffect(() => {
    console.log("Deliveries updated", deliveries, Date.now());
}, [deliveries]);

突变代码

addDelivery({
    variables: { location: locationObj, offset },
    optimisticResponse: {
        addDelivery: {
            __typename: "Delivery",
            id: `${Math.round(Math.random() * 999999)}`,
            createdAt: Date.now(),
            expectedEnd: null,
            routeId: null,
            location: {
                id: `${Math.round(Math.random() * 999999)}`,
                __typename: "GeoJSON",
                type: "Feature",
                geometry: {
                    __typename: "Geometry",
                    ...locationObj.geometry,
                },
                properties: {
                    __typename: "Properties",
                    ...locationObj.properties,
                },
            },
        },
    },
    update(cache, { data: { addDelivery } }) {
        cache.modify({
            fields: {
                deliveries(existingDeliveries) {
                    console.log("Modifying cache.", existingDeliveries, addDelivery, Date.now());
                    const newDelivery = cache.writeFragment({
                        data: addDelivery,
                        fragment: gql(`
                            fragment NewDelivery on Delivery {
                                id
                                location {
                                    id
                                    type
                                    geometry {
                                        type
                                        coordinates
                                    }
                                    properties {
                                        gid
                                        name
                                        street
                                        housenumber
                                    }
                                }
                                createdAt
                                expectedEnd
                                routeId
                            }
                        `),
                    });
                    return [...existingDeliveries, newDelivery];
                },
            },
        });
    },
});

Apollo Client配置

const cache = new InMemoryCache({
    typePolicies: {
        Query: {
            fields: {
                deliveries: {
                    merge(existing = [], incoming) {
                        return incoming;
                    },
                },
            },
        },
        Geometry: {
            keyFields: false, 
        },
        Properties: {
            keyFields: false,
        },
        GeoJSON: {
            fields: {
                properties: {
                    merge(existing = [], incoming) {
                        return incoming;
                    },
                },
                geometry: {
                    merge(existing = [], incoming) {
                        return incoming;
                    },
                },
            },
        },
    },
});

const client = new ApolloClient({
    link: splitLink,
    cache,
    connectToDevTools: true,
});

核心原因

  1. 缓存合并策略错误
    当前Query.deliveries的merge策略直接返回incoming(接口返回的完整列表),会覆盖缓存中已有的列表(包括乐观更新添加的临时对象)。首次乐观更新后,网络请求返回的完整列表覆盖缓存,后续乐观更新时,existingDeliveries是接口返回的列表,但乐观临时ID和真实ID不匹配,缓存合并时无法正确保留乐观数据,导致UI未感知到变化。

  2. cache.writeFragment的冗余使用
    乐观响应阶段写入的临时ID对象,在真实数据返回后会被替换,但update函数中用cache.writeFragment写入的对象引用未及时更新,导致列表中的引用还是临时ID的对象,UI无法触发重渲染。

  3. GeoJSON字段的merge策略类型错误
    GeoJSON的properties和geometry是对象类型,但配置中写成了数组合并,导致缓存处理时出现异常,间接影响UI更新。


解决方法

1. 修正缓存合并策略

调整Query.deliveries的merge逻辑为增量合并,保留现有数据并去重;同时修正GeoJSON字段的merge策略为对象合并:

const cache = new InMemoryCache({
    typePolicies: {
        Query: {
            fields: {
                deliveries: {
                    merge(existing = [], incoming) {
                        // 基于ID去重,合并现有数据与新数据
                        const merged = [...existing];
                        incoming.forEach(item => {
                            if (!merged.find(d => d.id === item.id)) {
                                merged.push(item);
                            }
                        });
                        return merged;
                    },
                },
            },
        },
        Geometry: {
            keyFields: false, 
        },
        Properties: {
            keyFields: false,
        },
        GeoJSON: {
            fields: {
                properties: {
                    merge(existing = {}, incoming) {
                        return incoming;
                    },
                },
                geometry: {
                    merge(existing = {}, incoming) {
                        return incoming;
                    },
                },
            },
        },
    },
});

2. 优化乐观响应与update函数逻辑

给临时ID添加前缀区分真实ID,去掉冗余的cache.writeFragment,直接合并数据并去重:

addDelivery({
    variables: { location: locationObj, offset },
    optimisticResponse: {
        addDelivery: {
            __typename: "Delivery",
            id: `temp-${Math.round(Math.random() * 999999)}`, // 临时ID加前缀
            createdAt: Date.now(),
            expectedEnd: null,
            routeId: null,
            location: {
                id: `temp-loc-${Math.round(Math.random() * 999999)}`, // 临时location ID加前缀
                __typename: "GeoJSON",
                type: "Feature",
                geometry: {
                    __typename: "Geometry",
                    ...locationObj.geometry,
                },
                properties: {
                    __typename: "Properties",
                    ...locationObj.properties,
                },
            },
        },
    },
    update(cache, { data: { addDelivery } }) {
        cache.modify({
            fields: {
                deliveries(existingDeliveries = []) {
                    // 去重后返回新数组,触发UI重渲染
                    const hasDuplicate = existingDeliveries.some(d => d.id === addDelivery.id);
                    return hasDuplicate ? existingDeliveries : [...existingDeliveries, addDelivery];
                },
            },
        });
    },
});

3. 调整查询的fetchPolicy(可选)

若无需每次从网络拉取完整列表,可将fetchPolicy改为"cache-first",减少网络请求对缓存的覆盖;若必须使用"cache-and-network",确保merge策略正确合并数据而非覆盖。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 21:52:04