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

如何使用Apollo Client 3与React实现数据实时展示及跨页同步

问题
  • 如何无需刷新网页即可实时展示数据库中的数据
  • 如何使用Apollo Client 3完成数据更新操作,实现新增分类后自动同步展示到所有其他页面

现有实现代码

全量分类拉取逻辑

async  componentDidMount() {
setInterval(async()=>{
  
  await client.query({
    query: LISTER_CATEGORIE 
  },   
  )
    .then((response) => {
      this.setState({ ...this.state, categories: response.data.listerCategorie });
    })
    .catch((err) => console.error(err));


},3000)
}

Apollo Client实例配置

export const client = new ApolloClient({
    link: ApolloLink.from([
      onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors) graphQLErrors.map(({ message, locations, path }) => console.log(`[GraphQL error]:
Message: ${message},
Location: ${locations}, Path: ${path}`));
  if (networkError) console.log(`[Network error]: ${networkError}`);}),
  new createUploadLink({ uri: 'http://localhost:8080/graphql'})
  ]),
    cache: new InMemoryCache({ typePolicies: {
      Query: {
        fields: {
          categorie(existingData, { args, toReference }) {
            return existingData || 
              toReference({ __typename: 'Categorie', id: args.id });
          },
        // 其余配置省略

新增分类实现

async  ajouterCategorie() {
await client.mutate({
  mutation: AJOUTER_CATEGORIE,
  variables: {
    titre: this.state.newCategorie.titre,
    presentation: this.state.newCategorie.presentation,
    image: '',
    dateCreation: new Date(),
    file: this.state.file,
    cours: []
  }, refetchQueries: [
    { query: LISTER_CATEGORIE }
  ], 
})
  .then((response) => {
    
    this.props.add(response.data.ajouterCategorie)
    this.pushNotify("Catégorie Ajouté avec succès", 'success', "Ajout Categorie")
  })
  .catch((err) => console.error(err));
this.handleClose()
}

问题原因
  1. 手写setInterval轮询不符合Apollo Client设计逻辑,裸调用client.query默认优先读缓存,即使定时触发也大概率拿到旧缓存值,且没有做组件卸载时的定时器清理,存在内存泄漏。
  2. refetchQueries只会触发指定查询的重新拉取,但其他页面如果用一次性的client.query拿数据,没有订阅查询结果变化,不会收到新数据触发重渲染。
  3. InMemoryCache没有配置分类列表字段的合并规则,新增数据无法自动归一化写入列表缓存,导致跨组件读缓存时拿不到最新值。
  4. 没有在新增分类的mutation回调里主动更新全局缓存,完全依赖refetch的网络请求,延迟高且不可靠。
解决方案

1. 替换手写轮询,用Apollo原生查询订阅能力

删掉手写的setInterval,用watchQuery发起查询,它会自动订阅缓存变化,内置轮询能力,且能在组件卸载时手动取消订阅避免内存泄漏:

async componentDidMount() {
  // 发起可观察查询,自动订阅缓存更新
  this.categoryQuery = client.watchQuery({
    query: LISTER_CATEGORIE,
    fetchPolicy: 'cache-and-network', // 先返回缓存值快速渲染,再拉网络数据更新
    pollInterval: 3000, // 需要轮询就开这个配置,比手写setInterval稳定
  });
  // 订阅数据变化,更新组件状态
  this.querySubscriber = this.categoryQuery.subscribe({
    next: (response) => {
      this.setState({ categories: response.data.listerCategorie });
    },
    error: (err) => console.error(err)
  })
}

// 组件卸载时必须取消订阅,清轮询
componentWillUnmount() {
  this.querySubscriber?.unsubscribe();
}

生产环境更推荐用GraphQL Subscription + WebSocket实现真正的实时推送,数据库数据变更时由服务端主动推送给所有客户端,没有轮询延迟,资源占用更低,需要后端配合支持WebSocket传输和订阅解析器。

2. 主动更新缓存,实现全页面数据同步

不要只依赖refetchQueries,在mutation的update回调里直接写入新数据到全局缓存,所有订阅该缓存的组件(包括其他页面)会自动触发重渲染,不需要等网络请求返回:

await client.mutate({
  mutation: AJOUTER_CATEGORIE,
  variables: {
    titre: this.state.newCategorie.titre,
    presentation: this.state.newCategorie.presentation,
    image: '',
    dateCreation: new Date(),
    file: this.state.file,
    cours: []
  },
  // 核心:手动更新缓存
  update(cache, { data: { ajouterCategorie } }) {
    // 读取当前缓存里的分类列表
    const existData = cache.readQuery({ query: LISTER_CATEGORIE });
    // 新分类追加到列表,写回缓存
    cache.writeQuery({
      query: LISTER_CATEGORIE,
      data: {
        listerCategorie: [...(existData?.listerCategorie || []), ajouterCategorie]
      }
    })
  },
  // refetchQueries可以保留做兜底,防止手动更新缓存出错
  refetchQueries: [{ query: LISTER_CATEGORIE }],
})

3. 补全缓存类型策略,保证归一化正常

在InMemoryCache配置里加上分类列表字段的合并规则,避免缓存更新时数据被意外覆盖:

cache: new InMemoryCache({ 
  typePolicies: {
    Query: {
      fields: {
        // 新增分类列表字段的合并策略
        listerCategorie: {
          merge(existing = [], incoming) {
            // 全量列表场景直接返回新值即可,如果是分页场景需要按分页参数合并
            return incoming;
          }
        },
        categorie(existingData, { args, toReference }) {
          return existingData || toReference({ __typename: 'Categorie', id: args.id });
        },
        // 其余原有配置保持不变
      }
    }
  }
})

注意事项

  • 所有页面读取分类列表时,统一用useQuery(函数组件)或watchQuery(类组件),不要用一次性的client.query,前者会自动订阅缓存变化,缓存更新时自动重渲染,后者不会。
  • 不要在Redux、Context等其他全局状态里重复存分类列表,直接从Apollo缓存读取即可,多副本存储很容易出现数据不一致问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 23:18:17