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

在Apollo + React中复用Mutation代码,避免重复实现

嘿,这个问题我太熟啦!在React Apollo里复用带乐观UI和缓存操作的Mutation,其实有几个非常实用的方案,我给你一步步拆解清楚:

1. React 16.8+最推荐:封装成自定义Hook

这是目前最灵活简洁的复用方式,把Mutation的定义、乐观UI、缓存更新逻辑全塞到一个自定义Hook里,任何组件都能直接调用,不用重复写冗余代码。

假设你的JoinLocation Mutation完整代码是这样的,我们来封装它:

import { useMutation, useQuery } from '@apollo/client';
import { gql } from 'apollo-boost';

// 先定义你的Mutation和关联的ME查询
const JOIN_LOCATION_MUTATION = gql`
  mutation JoinLocation($locationMachineName: String!) {
    joinLocation(locationMachineName: $locationMachineName) {
      id
      name
      members {
        id
        username
      }
    }
  }
`;

const ME_QUERY = gql`
  query Me {
    me {
      id
      locations {
        id
        name
      }
    }
  }
`;

// 核心:封装自定义Hook
export const useJoinLocation = () => {
  // 如果需要用到当前用户数据,这里可以顺便用useQuery获取
  const { data: meData } = useQuery(ME_QUERY);
  const currentUser = meData?.me;

  const [joinLocation, { loading, error }] = useMutation(JOIN_LOCATION_MUTATION, {
    // 你的乐观UI逻辑
    optimisticResponse: ({ locationMachineName }) => ({
      __typename: 'Mutation',
      joinLocation: {
        __typename: 'Location',
        id: `temp-${locationMachineName}`, // 临时ID用于乐观更新
        name: locationMachineName,
        members: currentUser ? [...currentUser.locations, {
          __typename: 'User',
          id: currentUser.id,
          username: currentUser.username
        }] : []
      }
    }),
    // 更新Apollo Store的逻辑
    update: (cache, { data: { joinLocation } }) => {
      const { me } = cache.readQuery({ query: ME_QUERY });
      cache.writeQuery({
        query: ME_QUERY,
        data: {
          me: {
            ...me,
            locations: [...me.locations, joinLocation]
          }
        }
      });
    }
  });

  return { joinLocation, loading, error };
};

然后在任意组件里直接用:

import { useJoinLocation } from './hooks/useJoinLocation';

const JoinButton = ({ locationMachineName }) => {
  const { joinLocation, loading, error } = useJoinLocation();

  const handleJoin = () => {
    joinLocation({ variables: { locationMachineName } });
  };

  if (loading) return <div>正在加入...</div>;
  if (error) return <div>加入失败,请重试</div>;

  return <button onClick={handleJoin}>加入这个地点</button>;
};

2. 兼容旧版本:封装成高阶组件(HOC)

如果你的项目还在使用React 16.8之前的版本,或者依赖HOC生态,那可以把Mutation封装成HOC:

import { graphql, compose } from 'react-apollo';
import { gql } from 'apollo-boost';

// 同样先定义Mutation和查询
const JOIN_LOCATION_MUTATION = gql`
  mutation JoinLocation($locationMachineName: String!) {
    joinLocation(locationMachineName: $locationMachineName) {
      id
      name
      members {
        id
        username
      }
    }
  }
`;

const ME_QUERY = gql`
  query Me {
    me {
      id
      locations {
        id
        name
      }
    }
  }
`;

// 封装HOC
export const withJoinLocation = compose(
  graphql(ME_QUERY, { name: 'meQuery' }),
  graphql(JOIN_LOCATION_MUTATION, {
    name: 'joinLocation',
    options: ({ meQuery }) => ({
      optimisticResponse: ({ locationMachineName }) => ({
        __typename: 'Mutation',
        joinLocation: {
          __typename: 'Location',
          id: `temp-${locationMachineName}`,
          name: locationMachineName,
          members: meQuery?.me ? [...meQuery.me.locations, {
            __typename: 'User',
            id: meQuery.me.id,
            username: meQuery.me.username
          }] : []
        }
      }),
      update: (cache, { data: { joinLocation } }) => {
        const { me } = cache.readQuery({ query: ME_QUERY });
        cache.writeQuery({
          query: ME_QUERY,
          data: {
            me: {
              ...me,
              locations: [...me.locations, joinLocation]
            }
          }
        });
      }
    })
  })
);

在组件里使用:

import { withJoinLocation } from './hocs/withJoinLocation';

const JoinButton = ({ joinLocation, locationMachineName, meQuery: { loading: meLoading } }) => {
  if (meLoading) return null;

  const handleJoin = () => {
    joinLocation({ variables: { locationMachineName } });
  };

  return <button onClick={handleJoin}>加入地点</button>;
};

export default withJoinLocation(JoinButton);

3. 跨场景复用:封装成全局Mutation函数

如果需要在非组件环境(比如工具函数、Redux Action)里调用这个Mutation,可以直接基于Apollo Client实例封装全局函数:

// 在你的Apollo Client配置文件里
import { ApolloClient, InMemoryCache } from '@apollo/client';
import { gql } from 'apollo-boost';

const client = new ApolloClient({
  uri: '/your-graphql-endpoint',
  cache: new InMemoryCache()
});

const JOIN_LOCATION_MUTATION = gql`
  mutation JoinLocation($locationMachineName: String!) {
    joinLocation(locationMachineName: $locationMachineName) {
      id
      name
      members {
        id
        username
      }
    }
  }
`;

const ME_QUERY = gql`
  query Me {
    me {
      id
      locations {
        id
        name
      }
    }
  }
`;

// 封装全局可用的Mutation函数
export const joinLocation = async (locationMachineName) => {
  // 先获取当前用户数据用于乐观UI
  const { data: meData } = await client.query({ query: ME_QUERY });
  const currentUser = meData?.me;

  return client.mutate({
    mutation: JOIN_LOCATION_MUTATION,
    variables: { locationMachineName },
    optimisticResponse: {
      __typename: 'Mutation',
      joinLocation: {
        __typename: 'Location',
        id: `temp-${locationMachineName}`,
        name: locationMachineName,
        members: currentUser ? [...currentUser.locations, {
          __typename: 'User',
          id: currentUser.id,
          username: currentUser.username
        }] : []
      }
    },
    update: (cache, { data: { joinLocation } }) => {
      const { me } = cache.readQuery({ query: ME_QUERY });
      cache.writeQuery({
        query: ME_QUERY,
        data: {
          me: {
            ...me,
            locations: [...me.locations, joinLocation]
          }
        }
      });
    }
  });
};

然后在组件或其他地方调用:

import { joinLocation } from './apollo/client';
import { useState } from 'react';

const JoinButton = ({ locationMachineName }) => {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const handleJoin = async () => {
    setLoading(true);
    try {
      await joinLocation(locationMachineName);
    } catch (err) {
      setError(err);
    } finally {
      setLoading(false);
    }
  };

  if (loading) return <div>加入中...</div>;
  if (error) return <div>加入失败</div>;

  return <button onClick={handleJoin}>加入地点</button>;
};

总结一下

  • 如果你用的是React 16.8+,优先选自定义Hook,它最灵活,没有HOC的嵌套问题,组件代码也更清爽。
  • 旧版本React可以用HOC过渡,兼容性好。
  • 需要跨组件/非组件场景调用的话,全局Mutation函数是最佳选择。

不管哪种方式,核心都是把Mutation的定义、乐观UI、缓存更新这些重复逻辑抽离到单独模块,避免到处复制粘贴相同代码~

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 03:31:09