1. 项目概述:当React Native遇见Apollo GraphQL

如果你正在用React Native构建移动应用,并且后端数据交互的需求开始变得复杂——比如需要从多个端点聚合数据、处理嵌套查询,或者实时更新UI——那么你很可能已经感受到了传统REST API的掣肘。这正是我几年前在开发一个社交内容类应用时遇到的真实困境。页面需要同时展示用户信息、其发布的内容流、以及内容的实时点赞数,一次渲染涉及三四个独立的API调用,状态管理混乱,性能也堪忧。直到我将Apollo Client引入React Native项目,整个数据层的体验才发生了质变。

“Scratching the Surface of Composition with React Native and Apollo”这个标题,精准地捕捉了这种技术组合的初期探索状态。它不仅仅是关于如何安装一个库,而是关于如何利用Apollo与React Native的声明式、组合式特性,从根本上重塑我们获取和管理数据的方式。Apollo GraphQL作为一个完整的数据管理平台,与React Native的组件化思想深度融合,允许开发者通过组合GraphQL查询片段(Fragments)来构建UI,实现数据需求与UI组件的精准对应。本文将深入探讨这一组合的核心实践,从基础集成到高级模式,分享我趟过的坑和总结出的最佳实践,目标是让你不仅能跑通一个Demo,更能理解其背后的设计哲学,并自信地应用于生产环境。

2. 核心架构与设计思路拆解

2.1 为什么是GraphQL + Apollo,而不是REST或普通状态管理?

在React Native生态中,数据获取和管理有多种选择:直接使用 fetch 调用REST API、采用 redux + redux-thunk/saga 、或者使用 React Query SWR 等现代钩子库。那么,Apollo GraphQL的独特价值在哪里?

首先, 精准的数据获取 解决了移动端的核心痛点——网络性能与数据流量。在REST模式下,一个“用户主页”可能需要调用 /user/:id /user/:id/posts /posts/:id/likes 等多个接口,容易导致过度获取(Over-fetching)或获取不足(Under-fetching)。GraphQL允许前端在一个请求中精确描述所需的数据形状,后端一次响应,极大减少了请求数量和冗余数据传输,这对于网络环境多变的移动设备至关重要。

其次, 声明式数据获取 与React的思维模型完美契合。使用Apollo时,你通过GraphQL查询语言“声明”组件需要什么数据,而不是“命令式”地编写何时以及如何获取数据的逻辑(如在 useEffect 中发起请求)。Apollo Client会自动处理请求的发送、缓存、更新和错误状态。这使得UI组件更加纯粹,专注于渲染,而数据层则变得可预测和可维护。

再者, 统一的数据管理层 。Apollo Client不仅仅是一个GraphQL请求客户端,它更是一个强大的应用状态管理库。它内置了归一化缓存(Normalized Cache),意味着从GraphQL查询回来的数据会被智能地存储在一个扁平化的结构中。当同一份数据(例如一个用户对象)在不同的查询中被引用时,缓存会确保它们指向内存中的同一份引用。任何位置的更新(如突变Mutation或订阅Subscription)都会自动更新所有相关的UI组件,实现了真正的全局状态同步,而无需手动编写繁琐的 reducer 逻辑。

最后, 强大的开发者体验 。Apollo Studio(原GraphQL Playground)提供了无与伦比的查询调试、性能监控和Schema探索能力。类型安全方面,通过与TypeScript或Flow以及GraphQL Code Generator工具链结合,可以实现从后端Schema到前端组件 props 的端到端类型安全,将许多运行时错误消灭在编译时。

2.2 Apollo Client在React Native中的架构定位

在React Native项目中集成Apollo Client,它通常扮演着“单一数据源”的角色。其架构位置如下图所示(概念性描述):

  1. 视图层(React Native Components) :使用 useQuery , useMutation 等Apollo React Hooks。
  2. 数据管理层(Apollo Client)
    • 链接(Apollo Link) :可组合的中间件链,用于处理请求生命周期(如设置身份验证头、错误处理、分页)。
    • 缓存(InMemoryCache) :归一化缓存,存储GraphQL查询结果。
    • 请求器(HttpLink) :通过HTTP与GraphQL服务器通信。
  3. 网络层 :实际的网络请求,由React Native的 fetch 或其它库处理。

这种架构将数据获取、缓存、状态更新的复杂性从UI组件中完全抽象出来。开发者只需关心两件事:组件需要什么数据(查询),以及组件想改变什么数据(突变)。剩下的,Apollo Client会帮你高效、一致地完成。

注意 :虽然Apollo缓存功能强大,但它并非要完全替代如 React Context Zustand 这样的本地状态管理方案。对于纯粹的、与服务器无关的UI状态(如模态框开关、表单草稿),使用本地状态管理通常更简单直接。Apollo更适合管理从服务器获取的、具有唯一标识符(id或 _id )的实体数据。

3. 环境搭建与核心配置详解

3.1 初始化项目与依赖安装

假设你已经有一个React Native项目(通过 npx react-native init 或Expo创建)。首先,安装核心依赖:

npm install @apollo/client graphql
# 或者
yarn add @apollo/client graphql

@apollo/client 是一个集大成包,包含了Apollo Client核心、React Hooks、内存缓存等所有必需模块。 graphql 是解析GraphQL查询语言的JavaScript实现。

对于更复杂的项目,我强烈推荐配置 GraphQL Code Generator 以实现类型安全。这需要额外安装:

npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo
# 初始化配置
npx graphql-codegen init

按照向导配置后,它会生成一个 codegen.yml 文件,用于自动从你的GraphQL Schema和操作(查询/突变)中生成TypeScript类型定义和对应的React Hook。

3.2 创建与配置Apollo Client实例

这是整个集成的核心步骤。你需要在应用的入口处(通常是 App.js App.tsx )创建Apollo Client实例,并将其提供给整个React组件树。

// App.js
import React from 'react';
import { ApolloClient, InMemoryCache, ApolloProvider, createHttpLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import AppNavigator from './navigation/AppNavigator';

// 1. 创建HTTP链接,指向你的GraphQL服务器端点
const httpLink = createHttpLink({
  uri: 'https://your-graphql-api.com/graphql', // 替换为你的API地址
});

// 2. 创建认证中间件链接(如果需要)
const authLink = setContext((_, { headers }) => {
  // 从安全存储(如AsyncStorage)获取token
  const token = await AsyncStorage.getItem('userToken');
  // 返回headers对象,将token添加到Authorization头
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
    }
  };
});

// 3. 链接组合:将认证中间件和HTTP链接串联起来
const link = authLink.concat(httpLink);

// 4. 创建Apollo Client实例
const client = new ApolloClient({
  link: link, // 使用组合后的链接
  cache: new InMemoryCache(), // 使用默认的内存缓存
  // 可选:启用开发工具(在Web上很有用,React Native中可能需要特定配置)
  // connectToDevTools: process.env.NODE_ENV !== 'production',
});

// 5. 主应用组件
export default function App() {
  return (
    // 使用ApolloProvider包裹整个应用,使client实例在所有子组件中可用
    <ApolloProvider client={client}>
      <AppNavigator />
    </ApolloProvider>
  );
}

关键配置解析:

  • InMemoryCache 配置 :默认配置适用于大多数情况。但对于更复杂的类型策略或缓存数据标识( dataIdFromObject ),你可能需要自定义。例如,如果你的数据使用 _id 而非 id 作为主键,需要配置:
    new InMemoryCache({
      typePolicies: {
        Query: { ... }, // 可以自定义根查询字段
        YourType: {
          keyFields: ["_id"], // 指定使用 _id 作为缓存键
        },
      },
    })
    
  • 链接(Link)组合 :Apollo Link的中间件模式非常强大。除了认证,你还可以添加错误处理链接(如 onErrorLink 处理GraphQL错误或网络错误)、轮询链接、或用于实现分页的 @apollo/client/link/persisted-queries 等。链接的执行顺序是从后向前(或从下到上), authLink.concat(httpLink) 意味着先执行 authLink 添加头信息,再执行 httpLink 发送请求。

3.3 处理React Native的特殊性

与Web环境不同,React Native需要特别注意:

  1. 网络权限 :确保 AndroidManifest.xml (Android)和 Info.plist (iOS)已配置必要的网络权限。
  2. 安全存储Token :上述示例中使用 AsyncStorage 存储token,但在生产环境中,应考虑更安全的方案,如 react-native-keychain expo-secure-store
  3. 开发工具连接 :在React Native中连接Apollo DevTools比在Web中复杂。一种常见做法是在开发环境中,将 uri 指向一个本地隧道服务(如 ngrok 暴露的地址),以便在浏览器中使用Apollo Studio。或者,使用 react-native-debugger 等工具。
  4. 离线与持久化 :对于离线优先的应用,可以考虑集成 apollo3-cache-persist ,将缓存持久化到设备的 AsyncStorage 中,应用启动时再水合(hydrate)到内存。

4. 核心操作:查询、突变与订阅的实战

4.1 数据查询(Query):声明式获取与UI绑定

使用 useQuery 钩子是获取数据的主要方式。它接受一个GraphQL查询文档,并返回一个包含 loading error data 等属性的对象。

// components/UserProfile.js
import React from 'react';
import { View, Text, ActivityIndicator, StyleSheet } from 'react-native';
import { useQuery, gql } from '@apollo/client';

// 1. 使用gql模板字面量定义查询
const GET_USER_PROFILE = gql`
  query GetUserProfile($userId: ID!) {
    user(id: $userId) {
      id
      name
      email
      avatarUrl
      posts(limit: 5) {
        id
        title
        excerpt
        likeCount
      }
    }
  }
`;

function UserProfile({ userId }) {
  // 2. 使用useQuery钩子执行查询
  const { loading, error, data, refetch, networkStatus } = useQuery(GET_USER_PROFILE, {
    variables: { userId }, // 传递查询变量
    notifyOnNetworkStatusChange: true, // 允许refetch时更新networkStatus
    // fetchPolicy: 'cache-and-network', // 缓存策略:先从缓存读,同时发网络请求更新
  });

  // 3. 处理不同的状态
  if (loading && networkStatus !== 4) { // networkStatus 4 表示 refetch
    return <ActivityIndicator size="large" style={styles.centered} />;
  }
  if (error) {
    return <Text>Error: {error.message}</Text>;
  }

  // 4. 渲染数据
  const { user } = data;
  return (
    <View style={styles.container}>
      <Image source={{ uri: user.avatarUrl }} style={styles.avatar} />
      <Text style={styles.name}>{user.name}</Text>
      <Text>{user.email}</Text>
      <FlatList
        data={user.posts}
        renderItem={({ item }) => <PostPreview post={item} />}
        keyExtractor={item => item.id}
      />
      {/* 手动刷新按钮 */}
      <Button title="刷新" onPress={() => refetch()} />
    </View>
  );
}

实操要点:

  • 缓存策略(fetchPolicy) :这是性能优化的关键。 cache-first (默认)优先从缓存读取,没有才请求网络; cache-and-network 同时读缓存和网络,快速显示旧数据再更新; network-only 总是请求网络; no-cache 不缓存结果。根据数据实时性要求选择。
  • 错误处理 useQuery 返回的 error 对象包含GraphQL错误( graphQLErrors )和网络错误( networkError )。应分别处理,并向用户提供友好的提示。
  • 重新获取(refetch) refetch 函数允许你手动触发查询,可以传入新的 variables 。结合 PullToRefresh 组件,可以轻松实现下拉刷新。

4.2 数据变更(Mutation):更新与乐观UI

突变用于创建、更新或删除数据。 useMutation 钩子返回一个元组: [mutateFunction, resultObject]

// components/LikeButton.js
import React, { useState } from 'react';
import { TouchableOpacity, Text } from 'react-native';
import { useMutation, gql } from '@apollo/client';

const TOGGLE_LIKE = gql`
  mutation ToggleLike($postId: ID!) {
    toggleLike(postId: $postId) {
      success
      message
      post { # 返回更新后的帖子数据,用于更新缓存
        id
        isLikedByViewer
        likeCount
      }
    }
  }
`;

function LikeButton({ postId, initialLiked, initialCount }) {
  const [isLiked, setIsLiked] = useState(initialLiked);
  const [likeCount, setLikeCount] = useState(initialCount);

  // 定义突变
  const [toggleLike, { loading }] = useMutation(TOGGLE_LIKE, {
    variables: { postId },
    // 乐观更新:在请求发出前立即更新UI,假设请求会成功
    optimisticResponse: {
      toggleLike: {
        __typename: 'ToggleLikeResponse',
        success: true,
        message: '',
        post: {
          __typename: 'Post',
          id: postId,
          isLikedByViewer: !isLiked, // 乐观地反转状态
          likeCount: isLiked ? likeCount - 1 : likeCount + 1, // 乐观地更新计数
        },
      },
    },
    // 更新缓存:请求完成后,用服务器返回的数据正式更新缓存
    update(cache, { data: { toggleLike } }) {
      if (toggleLike.success) {
        // 方式一:直接写入片段(推荐,精确)
        cache.writeFragment({
          id: `Post:${postId}`, // 缓存ID格式为 `类型名:唯一标识`
          fragment: gql`
            fragment LikeInfo on Post {
              isLikedByViewer
              likeCount
            }
          `,
          data: toggleLike.post,
        });
        // 方式二:也可以使用cache.modify进行更复杂的修改
      }
    },
    onError: (error) => {
      // 如果请求失败,回滚乐观更新
      console.error('Like failed:', error);
      setIsLiked(initialLiked); // 恢复原始状态
      setLikeCount(initialCount);
      // 可以在这里显示错误提示
    },
  });

  const handlePress = () => {
    if (loading) return;
    // 先乐观地更新本地状态,提供即时反馈
    setIsLiked(!isLiked);
    setLikeCount(isLiked ? likeCount - 1 : likeCount + 1);
    // 执行突变
    toggleLike();
  };

  return (
    <TouchableOpacity onPress={handlePress} disabled={loading}>
      <Text>{isLiked ? '❤️' : '🤍'} {likeCount}</Text>
    </TouchableOpacity>
  );
}

核心技巧:

  • 乐观更新(Optimistic UI) :这是提升用户体验的利器。在网络请求返回前,先根据预期结果更新UI和缓存。如果请求失败, onError 回调会触发,你需要负责回滚UI状态。Apollo的 optimisticResponse 选项能自动处理缓存的乐观更新。
  • 缓存更新(Update Function) :突变后,你需要告诉Apollo Client如何更新其缓存以反映数据变化。主要有三种方式:
    1. 自动更新 :如果突变返回的数据包含具有 id _id 字段的完整对象,且该对象类型已在缓存中存在,Apollo默认会 自动合并更新 该对象。这是最简单的方式。
    2. 使用 update 函数手动更新 :如上例所示,你可以精确控制如何修改缓存。 cache.writeFragment cache.modify 是常用API。
    3. 重新获取相关查询(Refetch Queries) :指定一个查询数组,在突变成功后重新执行。简单但可能不够高效。
  • onCompleted onError :用于处理突变完成后的副作用,如导航、显示通知等。

4.3 实时数据(Subscription):实现动态更新

对于聊天、实时通知、协同编辑等场景,需要使用GraphQL订阅。在React Native中实现订阅,后端通常使用WebSocket,前端需要配置 WebSocketLink

// 在Apollo Client配置中添加WebSocket链接
import { WebSocketLink } from '@apollo/client/link/ws';
import { split, HttpLink } from '@apollo/client';
import { getMainDefinition } from '@apollo/client/utilities';

const httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });
const wsLink = new WebSocketLink({
  uri: `wss://api.example.com/graphql`,
  options: {
    reconnect: true,
    connectionParams: {
      authToken: userToken,
    },
  },
});

// 使用split链接根据操作类型路由请求:订阅走WebSocket,其他走HTTP
const link = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    );
  },
  wsLink,
  httpLink,
);

在组件中使用 useSubscription 钩子:

import { useSubscription, gql } from '@apollo/client';

const NEW_MESSAGE_SUBSCRIPTION = gql`
  subscription OnNewMessage($channelId: ID!) {
    newMessage(channelId: $channelId) {
      id
      text
      sender { id name }
      createdAt
    }
  }
`;

function ChatRoom({ channelId }) {
  const { data, loading } = useSubscription(NEW_MESSAGE_SUBSCRIPTION, {
    variables: { channelId },
    // 当收到新数据时,更新缓存
    onData({ data }) {
      const newMessage = data.data.newMessage;
      // 通常在这里调用cache.modify将新消息添加到现有列表
      // 或者触发父组件的refetch
    },
  });
  // ... 渲染逻辑
}

注意 :React Native的WebSocket支持是原生的,但长连接在应用进入后台时可能被系统中断。你需要结合 AppState API和 react-native-background-timer 等库来处理重连逻辑,并考虑使用Apple Push Notification Service (APNs) 和 Firebase Cloud Messaging (FCM) 作为订阅的补充或后备方案,以节省电量并保证可靠性。

5. 高级模式与性能优化策略

5.1 查询组合与片段(Fragments)的使用

组合是GraphQL和React的核心优势。通过片段,你可以将UI组件的数据需求定义为一个可复用的单元。

// fragments.js
import { gql } from '@apollo/client';

export const USER_INFO_FRAGMENT = gql`
  fragment UserInfo on User {
    id
    name
    avatarUrl
    bio
  }
`;

export const POST_PREVIEW_FRAGMENT = gql`
  fragment PostPreview on Post {
    id
    title
    excerpt
    likeCount
    author {
      ...UserInfo # 嵌套使用片段
    }
  }
  ${USER_INFO_FRAGMENT}
`;

在查询中组合使用:

const GET_HOME_FEED = gql`
  query GetHomeFeed {
    feed {
      ...PostPreview
    }
  }
  ${POST_PREVIEW_FRAGMENT}
`;

在组件中,你可以直接使用定义了片段的查询。这带来了两大好处:一是保证了跨组件数据需求的一致性;二是当后端 User 类型字段变更时,你只需更新 USER_INFO_FRAGMENT 一处,所有使用它的查询都会自动获得更新(结合GraphQL Code Generator效果更佳)。

5.2 分页与无限滚动实现

处理列表数据是移动端的常见需求。Apollo提供了完善的 fetchMore API和 @connection 指令来支持分页。

query GetFeed($first: Int!, $after: String) {
  feed(first: $first, after: $after) {
    edges {
      cursor
      node {
        ...PostPreview
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

在组件中:

const { data, loading, error, fetchMore } = useQuery(GET_FEED, {
  variables: { first: 10 },
});

const loadMore = () => {
  if (!data.feed.pageInfo.hasNextPage) return;
  fetchMore({
    variables: {
      after: data.feed.pageInfo.endCursor,
    },
    // 关键:告诉Apollo如何合并新旧数据
    updateQuery: (prev, { fetchMoreResult }) => {
      if (!fetchMoreResult) return prev;
      return {
        feed: {
          ...fetchMoreResult.feed,
          edges: [...prev.feed.edges, ...fetchMoreResult.feed.edges],
        },
      };
    },
  });
};

// 在FlatList的onEndReached中调用loadMore

为了在缓存中正确合并分页数据,避免重复,建议在缓存类型策略中为查询字段设置 keyArgs 和合并函数,或使用 @connection 指令来标准化缓存键。

5.3 缓存归一化与数据一致性管理

Apollo的 InMemoryCache 默认使用 __typename id (或 _id )字段作为缓存的唯一标识符( dataIdFromObject )。理解这一点至关重要。

  • 优势 :无论你在多少个不同的查询中获取了同一个 User(id: "1") 对象,它们在缓存中都是同一份引用。一处更新(如突变更新了用户名),所有用到该用户的UI都会自动响应式更新。
  • 挑战 :如果你的数据没有 id 字段,或者标识符不标准,你需要通过 typePolicies 自定义 keyFields 。如果后端返回了非规范化的数据(如嵌套对象没有ID),缓存可能无法正确合并更新。

一个常见陷阱是列表项的更新 。假设你有一个帖子列表,你从详情页突变更新了某个帖子的标题。如果列表查询和详情查询都正确请求了帖子的 id ,那么列表中的对应项会自动更新。但如果列表查询只请求了部分字段,而突变返回了完整对象,缓存合并可能会产生意外结果。这时,你需要仔细设计查询和突变的返回字段,或使用 update 函数进行精细控制。

5.4 性能监控与调试

  1. Apollo Client Devtools :在开发中,利用浏览器扩展或React Native调试工具查看缓存状态、执行的查询和突变,是排查问题的第一选择。
  2. 查询性能分析 :使用 useQuery onCompleted onError 回调来测量请求耗时。Apollo Studio提供了生产环境下的性能跟踪,可以分析每个查询的解析时间、缓存命中率等。
  3. 避免不必要的重渲染 useQuery 返回的 data 对象在每次请求后都是一个新的引用,即使数据内容没变。如果将其作为 useEffect 的依赖项或传递给子组件的 props ,可能导致不必要的重渲染。可以使用 useMemo 对衍生数据进行记忆化,或确保子组件用 React.memo 进行适当的记忆化。
  4. 按需查询 :对于非首屏必需的数据,使用 useLazyQuery 进行懒加载查询。

6. 常见问题、排查技巧与实战心得

6.1 网络错误与GraphQL错误处理

错误处理必须区分网络层和GraphQL层。

const { error } = useQuery(MY_QUERY);
if (error) {
  // 网络错误(如无法连接)
  if (error.networkError) {
    console.error('Network error:', error.networkError);
    // 提示用户检查网络
  }
  // GraphQL错误(如权限不足、查询语法错误)
  if (error.graphQLErrors) {
    error.graphQLErrors.forEach(({ message, locations, path }) =>
      console.error(`[GraphQL error]: Message: ${message}, Path: ${path}`)
    );
    // 可能提示用户“操作失败,原因:XXX”
  }
}

实战心得 :建议创建一个全局的错误链接( ErrorLink )来统一处理认证失败(如401错误),自动跳转到登录页或刷新令牌。

6.2 缓存失效与数据更新难题

  • 问题 :“我明明执行了突变,为什么列表没刷新?”
  • 排查
    1. 检查突变是否返回了更新后的对象,并且包含了 id __typename 字段。
    2. 打开Apollo Devtools,查看突变执行后,缓存中对应对象的数据是否已更新。
    3. 检查列表查询是否请求了已更新的字段。
  • 解决
    • 确保突变返回完整标识和所需字段。
    • 使用 refetchQueries 强制重新获取列表查询(简单但可能低效)。
    • 编写 update 函数,手动使用 cache.modify 修改列表缓存。

6.3 React Native特定问题

  • “Can‘t find variable: Buffer” 或 WebSocket问题 :这可能是因为某些Apollo依赖的包在React Native环境中需要polyfill。解决方案通常是:
    npm install buffer
    
    然后在应用入口文件(如 index.js )顶部添加:
    global.Buffer = require('buffer').Buffer;
    
  • 应用后台运行时订阅断开 :需要监听 AppState 变化,并在应用回到前台时手动重启订阅或重新建立WebSocket连接。
  • 大缓存导致的性能问题 :使用 apollo3-cache-persist 持久化缓存时,如果缓存过大,读取和写入 AsyncStorage 可能阻塞主线程。考虑设置缓存大小限制或使用更快的存储后端(如 expo-secure-store 仅适用于小数据)。

6.4 类型安全实践

结合TypeScript和GraphQL Code Generator是提升开发效率和代码质量的终极武器。配置好后,每次修改GraphQL操作(查询、突变、片段),运行 npm run generate (或 yarn generate )即可自动生成对应的TypeScript类型和定制化的React Hook。这样,你在组件中使用 useQuery 时, data variables 都将具备完整的类型提示,彻底告别手写接口和运行时字段错误。

// 生成的Hook,直接使用,类型安全
const { data } = useGetUserProfileQuery({
  variables: { userId: '123' },
});
// data.user.name 是 string 类型,IDE自动补全

从REST API的繁琐手动管理,到GraphQL与Apollo的声明式数据流,这种转变在React Native开发中带来的效率提升和心智负担减轻是巨大的。它迫使你更清晰地思考组件的数据依赖,并通过缓存归一化自动维护全局状态的一致性。当然,初期的学习曲线和架构复杂度是存在的,尤其是缓存更新策略和错误处理的精细化。但一旦掌握,你会发现构建复杂、响应式的数据驱动型应用变得前所未有的顺畅。我个人最大的体会是,将业务逻辑从组件中剥离到GraphQL查询和Apollo缓存层后,组件的可测试性和可维护性显著提高。最后一个小建议:从一个小型功能开始试点,比如用户个人资料页,逐步熟悉查询、突变和缓存更新,再将其模式推广到整个应用,这样能更平稳地驾驭这套强大的组合。

更多推荐