哦,你好,Apollo Client,再见,Redux!
我知道我对这个标题有点兴奋,但这确实有点道理😅。在这篇博文中,我将解释为什么切换到 GQL 和 Apollo Client 3 应该让你放弃 Redux。我还会分享我从 Redux 到 Apollo Client 的历程。
我曾经有过一些疑虑,所以在过去的几个项目中,我对使用Apollo Client作为状态管理解决方案非常怀疑。我爱 ❤ Apollo,尤其是 Apollo Client 3 中的一些改进彻底改变了我的想法 😻
我为什么喜欢Redux以及它的优点 💙
- 全局状态管理解决方案,让您可以清晰地了解整个状态
- 您使用操作来触发状态更新和异步请求(爱💌我的宝贝:Redux saga🔗)
- 整个生态系统非常棒,你还可以使用Redux时间旅行进行调试!⏲
- 您可以使用Redux选择器(另一个很棒的库🔗)之类的库从状态中选择数据并进行转换
这就引出了我的下一个观点……👇
什么才算是好的状态管理解决方案?✅
- 我的数据已标准化(请勿重复🙏)
- 具体操作,即用户登录/路由应该能够触发异步请求💯
- 我们希望转换数据,这样我们的组件就不会太大,而且我们可以编写测试!🍵
- 最后,可视化存储,即我们可以查看全局状态并轻松调试🌎
我相信还有很多,但以上是我列表中排名靠前的!🥇
在我开始使用 GQL 之后✨
- 我没有在 GQL 项目中使用 Redux,因为我们使用的是 React Hooks 和 React Context,而且感觉重复,因为您可以使用 useReducer 和 useContext 来调度操作和更新状态
- Apollo Client 公开了自定义钩子 ⚓️,例如 useQuery 和 useMutation,它们会自动公开加载、成功和错误状态,因此我无需在 Redux Store 中触发 3 个不同的操作,即 CART_REQUEST、CART_SUCCESS 和 CART_ERROR。例如,这里有一个比较 ⚡️
大量样板代码已经减少😈您可以直接从 useQuery 和 useMutation 钩子中获取加载、成功和错误状态。
那么缺少了什么?😅
回到优秀状态管理库的定义👆
- 我真的很喜欢 useQuery 和 useMutation 自定义钩子,尽管我并不完全相信要完全切换到状态管理,因为我真的很喜欢使用Redux 选择器来选择数据并且我们有能力对其进行转换😥
- 与此同时,我使用React Context而不是Redux
- 我也不想一直读阿波罗缓存
- 当时,没有办法将值存储在缓存之外
- 我还希望操作能够触发像Redux saga那样的异步请求🚶♀
- 除此之外,我发现 Apollo 客户端缓存真的很难读取😫
但是在 Apollo Client 3 中,他们引入了 反应变量和本地字段,改变了一切💖
Apollo Client 3 为我们提供了 2 个非常酷的东西 😎
- 仅限本地字段
- 反应变量
它们是客户端自行解析的字段,可以通过从缓存中读取数据来替换 Redux 中的转换器。让我们看看它是如何工作的。
我的数据已标准化(请勿重复🙏)
Apollo Client帮你处理了繁重的工作💪。你无需频繁地 dispatch action 来更改状态。有了 Redux,我们已经习惯了这种做法,而且它的好处是你可以完全掌控,不过我们真的需要完全掌控吗?😶
您已经在使用 GQL ❤️,所以一切都是图📈,并且存储在图中。也就是说,您已经在缓存中拥有所有数据,那么为什么还要在上面添加 Redux 来复制它呢?🤷♀ 您会增加更多开销🙈
Apollo 客户端会自动缓存您的数据,并在查询响应和变更后对新数据进行规范化。这与您在 Redux 中所做的操作类似,您需要确保数据已规范化。如果您正在引导一位新开发人员,这会比较困难,因为他们还需要在架构层面考虑和学习如何做到这一点,这会增加额外的开销。

Apollo 客户端使用引用来存储数据,因此它可以智能地使用该引用作为键轻松查找数据。这里有一篇很棒的博客文章🔗,由 Khalil Stemmler 撰写,主题是《揭秘 Apollo Cache》,在切换到 AC3 进行状态管理之前,你应该先阅读一下。💯
数据转换😄
我们希望在应用程序中进行数据转换,因此需要将副作用层与转换层明确分离。这样,我们就可以确保组件文件不会很大,并且我们可以轻松地为这些转换器编写测试。
https://medium.com/media/bcb60b1b989a751e19eb3c6117889e25/href
我们将主要使用本地字段来转换数据。
1. 仅限本地字段🌼
仅限本地字段是一种在 GQL 类型上定义客户端字段的方法,这些字段无需来自服务器。您可以在前端本地解析它们。
假设我们有以下查询来获取用户的购物车⚡
以下是上述查询中的购物车查询数据对象的样子👈
假设我们有这个用户故事,💄
作为用户,我想根据购物车中的商品查看某件商品是否缺货或库存不足。👩🏽💻
在不使用Apollo客户端变量的情况下,您的React组件可能如下所示:💄⚡️
通常在Redux中,我们会使用 redux 选择器将 getTextForLowOrOutOfStock 函数的逻辑提取到外部。🆗
使用 AC3,您可以通过读取缓存并在客户端中相应地添加“缺货”和“库存不足”字符串来实现上述目的。
好的,但是我们如何使用仅限本地的字段呢?🤔
我们可以使用 @client 指令在Cart 类型上创建仅限本地的字段。🎉例如, ⚡️ 这里的 stockText 是客户端字段。
使用 @client 指令后,Apollo 客户端会查找缓存来解析该字段。由于该指令的作用,它不会通过网络调用该字段。现在,只要我们声明一个 Cart 类型,就可以随时访问 stockText ,因为它是 Cart 类型的一个字段。
现在我们可以通过执行以下操作直接访问 React 组件中的 stockText⚡️
2. 反应变量
我们还可以创建存储在缓存之外的自定义客户端值,称为“响应式变量”。有时我们只想在类型结构之外创建一个字段,该字段仍然可以通过Apollo 客户端全局访问。为此,Apollo 客户端为我们提供了“响应式变量”。
修改反应变量会触发依赖于该变量的每个活动查询的更新,以及与useReactiveVar React 钩子返回的任何变量值相关的反应状态的更新。
响应式变量不会更新缓存,而是存储我们想要在应用程序中随时访问的状态信息。在Redux中,我们通常会调度一个 action 来将这样的值存储在 store 中。
假设我们有这个用户故事,💄
作为用户,我想查看购物车中正在销售的商品数量。💯 👩🏽💻
为此,您需要读取购物车中的所有商品,检查正在促销的商品并进行计数。使用 Apollo 客户端,您可以通过以下方式实现此目的👇⚡️
你可以做的远不止这些。你还可以通过其他字段访问现有字段(例如 readNumberOfOOSItems)。🙌
您也可以通过查询访问上述 readNumberOfOOSItems,它会为您提供加载、数据和错误状态:
但是等等,局部字段和反应变量之间有什么区别?🤔
在仅限本地的字段中,您可以在类型本身上创建一个新字段,即从我们的示例中,我们在 Cart 类型上创建了 stockText,即您无法在其他任何地方访问 stockText。
但对于反应变量,你可以在任何你喜欢的地方访问它,而且它不受特定类型的限制。与 Apollo 客户端缓存不同,反应变量不强制数据规范化,这意味着你可以以任何你想要的格式存储数据。🤯
特定操作应触发异步请求⏩
一旦我们检索到数据或者用户想要根据来自服务器的某些信息进行路由,我们可能想要触发异步请求或者用户应该采取的特定操作。
假设我们有这个用户故事,💄
作为用户,如果我未登录,我希望被带到登录页面,否则我想查看应用程序页面
这里我们想要跟踪用户是否已登录,并据此路由用户。我们可以通过创建一个响应变量来实现。
响应式变量是存储在客户端中且位于缓存之外的变量,但组件也可以直接访问它们的值。在下面的示例中,isUserLoggedIn是一个使用 makeVar 函数创建的反应式变量。它调用该函数来检查浏览器Cookies中是否存在令牌🍪。(在现实世界中,我们当然也会检查令牌是否过期 😉)。
字段下的所有内容都属于字段策略。字段策略本质上是客户端与函数之间的契约,它规定了该字段的解析方式。我们有一个字段策略,用于读取缺货商品的数量并检查用户是否已登录。
接下来,为了在组件内访问此值,我们可以执行以下操作⚡️
每当 isUserLoggedInVar 的值发生变化时,上述内容都会重新渲染
如果您想在用户登录后触发API 请求
,可以通过在 useEffect 中监听 isUserLoggedIn 来实现。👈 因此,我们可以根据状态触发异步请求。
但是等等,我可以更新 Reactive 变量的值吗?🤔
是的,可以!我们可以在应用程序的任何地方更新反应变量的值,例如,如果我们想将 isUserLoggedInVar 的值更新为 false 或其他值,我们就可以!我们只需要直接调用 isUserLoggedInVar 函数!
可视化存储/缓存🔮
最后,可视化存储,即我们可以查看全局状态并轻松调试
和Redux 开发者工具一样,Apollo 客户端也有自己的开发者工具,链接如下。🔗 最初,我在可视化缓存方面遇到了一些困难,因为 Apollo 开发者工具不如 Redux 开发者工具成熟。
但在理解了 Apollo 客户端内部如何存储数据以及如何优化数据之后,事情变得简单多了。我能够将缓存可视化了。😄
在“查询和修改”选项卡中,你将看到应用程序中执行的查询和修改列表(就像 Redux 一样)。在“缓存”选项卡中,你将看到整个缓存,即你的根查询以及已更新的缓存引用。
你可以使用GraphiQL查询任何内容(包括 Reactive 变量),就像在 GQL 实践中一样。但如果你想查询 Reactive 变量,请务必勾选“从缓存加载”复选框。
我发现Redux 开发工具在时间旅行方面更胜一筹,不过一旦你了解了缓存的工作原理以及它是如何为你处理繁重工作的,它就会变得简单得多。但我认为这绝对是Apollo 客户端开发工具的一大痛点🤕。
最后,保持开放的心态
https://medium.com/media/7f446247325b2b814408d4727aaf4695/href
- Redux和Apollo Client之间的区别在于,你要么自己控制并完成所有事情(例如 Redux),要么让像 Apollo Client 这样的成熟库为你处理。
- 别误会,我确实喜欢控制😂。但Apollo 客户端会为你处理大部分工作,这样你就可以专注于应用程序的核心
- 我一直将Apollo 客户端与Redux进行1:1 的比较,虽然它能很好地帮助我理解我的应用程序如何扩展,但这也是我犹豫不决的原因,因为现在我必须忘记我所学过的东西,并相信Apollo 客户端会为你处理好这一切。👌
- 当你使用 Apollo 客户端时,在其上使用 Redux 确实感觉是多余的,因为你现在保留了相同数据的两个副本,即 Apollo 客户端缓存和 Redux 全局存储。🙈
- 你对缓存了解得越多,你就会越喜欢它!❤️
感谢您读到这里,希望这篇文章对您有所帮助💯,并帮助您比较 Redux 和Apollo Client。🙌
文章来源:https://dev.to/kulkarniankita9/oh-hello-apollo-client-goodbye-redux-4dpi