在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
相关产品推荐
相关产品推荐

