想向世界各地的前端工程师宣传Apollo Client
你好。请问你的编码进展如何?
最近,我开始尝试使用GraphQL,并且也遇到了Apollo Client。我感到兴奋,因为我意识到它将对现有的前端设计和实现产生比我想象中更大的影响。
你以为Apollo Client只是一个GraphQL客户端,对于没有GraphQL端点的我来说无关紧要。但是,Apollo Client不仅仅是这样!!!!!
在本文章中,我将讨论”Apollo Client是什么”以及”为什么我想宣传Apollo Client”这两个问题。实际上,我最初是在编写包括实现在内的教程,但由于篇幅过长,所以将文章分为两篇。这篇文章主要是关于概念的讨论,另一篇文章则是关于Apollo Client + React教程的,希望你能一起阅读。
希望通过这篇文章能够引起更多人的兴趣。Apollo Client版本2发布才半年左右,最佳实践还没有完全成熟,尤其在日本,文章本身都很难找到。
GraphQL 为一种查询语言
在了解Apollo Client之前,需要先了解什么是GraphQL,幸好已经有很多优秀的文章详细介绍了GraphQL,所以我只会提供一个链接。
- アプリ開発の流れを変える「GraphQL」はRESTとどう違うのか比較してみた
尽管上面提到的是关键点,但在这里还想强调一下GraphQL的一个重要特征。
这意味着GraphQL是一种“通用”的查询语言。首先,GraphQL本身不是指特定的框架或技术,而是描述针对数据的查询语言规范。此外,发送这个查询的目标不仅限于GraphQL客户端。根据不同的实现,甚至可以在浏览器缓存或REST API中处理GraphQL查询。
我希望稍后与Apollo Client的生态系统一起介绍这个问题,所以请先把“万能”这个关键词记住。
Apollo Client是什么
Apollo Client是一个用于在客户端简化操作GraphQL API的库。
我在前面的另一篇文章中详细说明了实现细节,但在这里我也会简单地提供一些代码示例。
首先,通过使用Link来设置使用GraphQL端点。
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import App from './App';
const GITHUB_BASE_URL = 'https://api.github.com/graphql';
const httpLink = new HttpLink({
uri: GITHUB_BASE_URL,
headers: {
authorization: `Bearer ${
process.env.REACT_APP_GITHUB_PERSONAL_ACCESS_TOKEN
}`
}
});
const cache = new InMemoryCache();
const client = new ApolloClient({
link: httpLink,
cache
});
如果我们使用Appollo Provider将整个应用程序组件包含在内,那么准备工作就完成了。(请将其视为与react-redux的Provider完全相同的功能)这样,下面的组件就可以引用前面设置的客户端了。
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root')
);
在GraphQL端点上连接数据和组件的方法如下。
const GET_CURRENT_USER = gql`
{
viewer {
login
name
}
}
`;
const User = () => (
<Query query={GET_CURRENT_USER}>
{/* Render Prop パターンでクエリの結果にアクセスしています */}
{({ data, loading, error }) => {
if (error) {
return <ErrorMessage error={error} />;
}
const { viewer } = data;
if (loading || !viewer) {
return <Loading />;
}
return (
<div>{viewer.name}</div>
);
}}
</Query>
);
Apollo Client是一個使得對GraphQL端點的查詢變得簡單,並且支援與React、Vue等UI庫的整合的工具。正如前面所提到的,GraphQL是一種”通用”的查詢語言,可以用於除了GraphQL API以外的數據來源,包括本地數據和REST API。
阿波罗链接状态
Github: apollo-link-state
Github:apollo-link-state
实现之前所说的“通用查询语言”的关键是这个名为apollo-link-state的库。apollo-link-state是Apollo Client的一个库,正如其名,它提供了一个可以使用GraphQL查询处理本地数据的存储库。
首先,让我们介绍一种简单的实现方式。首先要设置链接(Link)如下所示,并在此时指定初始状态。
import { withClientState } from 'apollo-link-state';
const initialState = {
selectedItemId: 'hogehoge',
};
const stateLink = withClientState({
cache,
defaults: initialState,
resolvers: {},
});
const link = ApolloLink.from([stateLink, httpLink]);
const client = new ApolloClient({
link,
cache,
});
在发出查询时,您可以使用 @client 指令来指示“这些数据存在于本地”,从而允许对本地数据使用GraphQL查询。
const GET_SELECTED_ITEM = gql`
query {
selectedItemId @client
}
`;
顺便提一下,您还可以在一个GraphQL查询中同时指定来自GraphQL端点的数据和本地数据。
const GET_SELECTED_REPOSITORIES = gql`
query {
selectedItemId @client
item {
name
description
}
}
`;
apollo-link-rest (阿波罗链接REST)
Github: apollo-link-rest
GitHub:apollo-link-rest
apollo-link-rest可以让我们在REST API中使用GraphQL查询,正如其名。因此,理论上来说,即使没有GraphQL服务器,我们也可以在客户端使用GraphQL!!!!
嗯,虽然我们想要这样做,但鉴于apollo-link-rest在其README中标注了“⚠️该库正在积极开发中⚠️”,它是否可作为生产选项还是有些微妙。
在使用apollo-link-rest时,操作方式与apollo-link-state相似,只需要添加专用的Link,并在发出查询时指定@rest指令即可。
为什么Apollo Client能让我们的灵魂震颤?
可能有一些人已经感受到了灵魂的颤动,那么现在我将讲述为什么我想要宣传Apollo Client这个主题
根据结论来说,原因在于”在状态管理中,客户端代码变得简单”。
许多人可能会认为GraphQL是一种服务器端的查询语言,与状态管理有什么关系呢?
我会按顺序进行解释。
首先,我会解释一下状态管理的概念。就我个人而言,我认为前端状态管理是以下这样一个世界观。(这只是我的观点,请随时提供反馈和评论。只要我能保持冷静,就会回应。)

在设计的时候,有三个重要的观点需要考虑。
1. 数据流管理 liú lǐ)
在现今的前端应用中,控制单向数据流以减少错误并提高状态转移的可预测性已成为主流,用于实现这种流程控制的架构有flux和Rx等。
2. 与数据源同步并随之进行的异步处理
前端代码很少能独立完成,通常需要从外部获取数据或根据用户的操作进行数据同步处理。虽然Redux声称是”Single source of truth”,但这只适用于本地数据,其源头还可以是其他的。此外,某些要求可能会导致复杂的异步状态变化,为了处理这些情况,可以使用Rx或Redux的中间件等工具。
3. 从状态中展现用户界面
状態会最终反映在 UI 中。多数时候由于虚拟 DOM 可以很容易地将状态与 DOM 进行映射,因此只需要编写状态与 DOM 的映射即可应对大多数情况,然而有时会发生复杂的状态转换无法只依靠这种方法解决的情况,这时组件设计模式或状态机的思想就变得很有用了。关于这点需要说明的是,与 Apollo 客户端关系不大,并且坦率地说,关于这个话题我没有太多具体得到决策,所以在这篇文章中不再展开讨论。
那么,如果在上图中引入 Apollo Client,会变成这样。

用中文简洁地概括变化,就是”过去编写与状态变化相关的逻辑要使用GraphQL查询以声明方式进行编写”。
我会具体说明一下。在我看来,从执行Action到更新View的部分逻辑可以大致分为以下四种类型。
-
- 查询和同步数据源
-
- 将本地数据加工成适用于数据源请求参数的形式
-
- 将获取的数据加工成视图易于使用的形式
- 根据行动更新本地数据
如果我们告诉他们这些中的1至3可以通过查询以陈述的方式表达出来,那么简洁性就能得到传达,你认为呢?
首先,对于第一个问题”1. 数据源的查询和同步”,只需要在Apollo Client的主对象中定义,之后就不需要再担心了。
在处理GraphQL客户端时,有两个重要角色,即”模式(Schema)”和”解析器(Resolver)”。模式就像其名称所示,它定义了我们在查询中要获取的对象的模式。而解析器定义了如何将模式与实际的数据源进行映射。
通过这个Schema Resolver的定义和Apollo Client的Link设置,客户端的代码不需要知道“数据在哪里以及是什么样的”,只需要简单地发送查询即可(严格来说,需要添加@client或@rest之类的注解)。
这样一来,我们就能够(从客户端代码的角度)真正获得单一真实数据源。
关于第二点和第三点,由于GraphQL的起源初衷,应该不言而喻。只需要简单地编写适用于View的查询语句即可。虽然看起来很简单,但代码量的减少是难以估量的。如果你平时使用Redux或者Vuex编写代码的话,想象一下数据处理部分全部消失后会带来多大的变化,你就能明白这个改变有多么大了。
第四个也是轻松的!虽然我想说这一点已经变得毫无悬念了,但对于这一点,遗憾的是并没有与传统的做法有着戏剧性的变化。很遗憾的地方,我考虑过是否省略这一点,但出于良心,我还是会解释一下。由于有点多余,所以如果觉得遗憾的话,可以适当地跳过阅读。
从根本上讲,”随着操作更新本地数据”指的是什么,举个例子就是Qiita的”赞”按钮。
当按下Qiita的”赞”按钮时,会向服务器发送更新请求。
同时,客户端上的数字也会相应增加(请试着在本篇文章中按下按钮试试看…瞧!)。
实现这一目标的方法有几种。
-
- アップデートのリクエスト送った際に最新の数を返すようなAPIに設計する
-
- アップデートのリクエストを送った後にいいね数を再フェッチする
- いいねを押した時にクライアント側で 1 増やす処理を行う
我觉得我们应该选择第三个选项,并尝试使用这个策略来编写实现示例。
如果要在Apollo Client中实现这个,请参考以下示例。
const updateLikes = (
client,
{
data: {
updateLikes: {
article: { id }
}
}
}
) => {
const article = client.readFragment({
id: `Article:${id}`,
fragment: ARTICLE_FRAGMENT
});
const likeCount = article.likeCount + 1;
client.writeFragment({
id: `Article:${id}`,
fragment: ARTICLE_FRAGMENT,
data: {
article: {
totalCount
}
}
});
};
<Mutation
mutation={LIKE_ARTICLE}
variables={{ id }}
update={updateLikes}
>
{(likeArticle, { data, loading, error }) => (
// 何かしらの処理
)}
</Mutation>
虽然没有那么麻烦,但也没有变得轻松。
有没有办法实现一种声明性地写下数据和数据之间关系的方式呢?
总结
话题已经扩散了,但总结起来,我希望传教的原因主要在于”与状态管理相关的逻辑简单化”。虽然这还是一项新技术,但思路似乎是合理的,而且社区的热情也很高,所以我非常期待。
专栏:GraphQL是否会取代Redux?
偶尔会在海外看到一些标题像“GraphQL取代Redux!”这样的说法,但更准确的应该是“Apollo Client取代Redux”。
至少就通讯方面而言,Apollo Client将全面承担角色。至于剩余的本地状态,Redux可能还能发挥一些价值。
我个人认为,仅仅使用React原生的状态管理就足以处理大多数情况,这种情况下使用Redux可能会过度工程化。此外,正如我在文中多次提到的,通过apollo-link-state也可以管理本地状态,所以我还不能确定这是否是一个有希望的解决方案,但我认为完全可以使用Apollo Client来管理所有内容。
這是為那些想要學習的人提供的資料。
虽然我已经多次赞扬过了,但我仍然感觉到学习GraphQL的初始成本相对较高。尽管GraphQL本身已经面世三年了,但它仍然是一种相对较新的范式,对于查询规范的熟悉可能需要一些时间。
不过遗憾的是,日语的资料几乎无法找到。因此只能以英语呈现,但我会放上一些对学习非常有帮助的资源。
-
- Apollo チームブログ
-
- Apollo Documentation
- awesome-graphql
嗯,虽然只有一种选项,但这里只介绍了一些似乎是官方的东西,这让人觉得有点悲伤,不过我们要加油!
作者的话
阿波罗客户端你觉得如何?
我个人认为,尽管我对“把所有状态都交给Apollo难道不会变得更加复杂吗?”或者“当本地数据急剧增加时,文档的运作和执行是否还能顺畅进行?”有些疑问,但毫无疑问地,我认为这将成为前端状态管理的下一个趋势之一。
有一个GraphQL服务器的环境中,作为前端的GraphQL客户端,Apollo Client正逐渐成为唯一的选择,所以如果选择了Apollo Client,将状态管理机制交给它也是可以的。
我很高兴地看到阅读这篇文章后,愿意与我一同成为先试者的人增加了^^
那么,祝你编程愉快!