GraphQL还是通过Http的GET和POST的方式返回数据,只是GET的长度限制导致可能的查询会出问题。所以一般都可以用POST来获取、修改数据。这就是说GraphQL在客户端App来说可以和平时请求API的方式完全一样。在基本使用上,有没有第三方graphql client的库的帮助都没什么区别。
GraphQL是啥
在正式开始之前,稍微介绍一下GraphQL。如果你的项目稍有规模,那么你一定经受过一种折磨。一个很久之前的API返回了巨多务必的数据,是可以完全服务现在的需求。但是明显数据过多在要求性能的时候,在后端数据是查出来的,有缓存也得访问了缓存才能返回并不是没有代价。在前端占了带宽返回就慢。然后从一大堆数据里拿出你想要的也要代价。后面的维护对于前后端都是可能产生棘手的问题。之所以FB要提出GraphQL的标准也是因为FB本身支持的产品太多遇到了这样的问题。
如果客户端这边说有了什么需求,就获取这个需求的必要数据,那么基本就要新开发API。GraphQL就是一个你要啥就返回啥的巨大API。可以查询你指定的数据,也可以修改后端的数据。查询就是Query
,修改操作叫做Mutation
。
查询:
query {
todos {
id
title
}
}
这是一个查询。要查询的是todos(可以暂时理解为一个表),要查询的是id
和title
两个字段。这个查询只会返回id
和title
两个字段对应的数据。
也可以是带条件的查询:
query ($from: Int!, $limit: Int!) {
todos (from: $from, limit: $limit) {
id
title
}
}
新增、修改
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
返回
{
"data": {
"createReview": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
}
基本介绍就到这里。详细可以参考官方文档。
为啥不是Relay
官方的库专职负责劝退有没有体会过。GraphQL是Facebook提出来的一个标准,注意这是一个标准而不是实现。服务端的情况不熟不多做介绍,但是在客户端FB或者现在叫Meta了,给出了一个实现并且已经发展了很多年。这个库叫做Relay。
它凭借强大的功能和Meta(当年FB)背书,很快发展了起来。不过这个工具显然已经有点后劲不足了。从现在TypeScript项目发展的情况来看,它显然缺乏对TypeScript的支持。比如它的一个配套babel插件没有对TypeScript的支持。当然也是一个小问题,只需要在自己的项目里添加一个index.d.ts文件并添加类型就可以。
然后是它的模式。在你按照官方文档的Step by step一步一步走完的话,你还是不能做开发。因为你在添加另外一个文件的和查询的时候就会发现,这个需要的查询并不会自动生成。要么是悄没声的没有报错也没有生成对应的文件,要么是报一些莫名其妙的错。因为你需要根据你的文件名来命名查询(或者任何的操作)。也就是它的模式可以认为是强侵入的,虽然会比其他的方式少写一些固定代码,虽然也不一定。笔者水平有限,只好先弃了。
URQL怎么样
首先,urql在github有6.5K的star。并且设计也足够活跃。最后被后还有个公司支持。不能说不是KPI项目,但是KPI项目也有个好处,至少有为了KPI的人在维护代码。
另外,这个库是用TypeScript开发的。也就是说它肯定是TypeScript友好的,你的项目如果用了TypeScript,在类型上不用担心过时、不完整等问题。
并不是其他的库不合适,更多可以选择的库在GraphQL官网里有列出来。
用一下现成的GraphQL服务:Github
Github很久以前就提供了GraphQL API。我们就在APP里调用github的GraphQL API。用来查询某个owner(比如facebook)下面的公开代码库。
要使用github的GraphQL服务需要用到token,所以需要一个输入token的界面。在用户输入token后可以持久化存储这个token。还需要有一个界面可以删掉这个token,让用户有机会可以输入新的token。尤其用户修改了权限之后,那么就必须要有一个更新token的地方。
导航
用户在进入APP之后,在点击Repo选项之后,!如果不存在这么一个Token,则会进入token页面。在用户输入token之后才能继续后面的功能。
用户在输入Token页输入token后跳转到列表页。在Settings页可以删掉Token,然后自动跳转到Token页。
在用户成功输入token,进入repo列表页可以看到repo列表。现在只显示facebook下面的公开repo。后面加入search bar可以输入owner,这样就可以控制要搜索的是哪些repo了。
URQL基本配置
urql的配置分两部分。第一是provider的配置。使用provider可以让所有调用graphql api的地方都很方便的拿到请求的endpoint和需要的auth token。当然不是明文的读取而是可以直接调用查询。
配置Provider
在App.tsx可以看到:
这个Provider和react-redux的provider的作用一样。这里urql的provider提供的是一个client。
Exchange
Exchange是urql的一个中间件机制。这个机制也和Redux的中间件机制类似。
这里我们需要给官网提供的authExchange
填空,把获取和使用token的逻辑加进去。
首先需要安装authExchange
:
yarn add @urql/exchange-auth
然后在路径:src/data/graphqlClient.ts下可以看到给authExchange
填空的代码。在这里需要添加的除了上文说的获取token,使用token之外还有错误处理的内容。之类为了简单,错误处理的部分先忽略。有需要的同学可以研究官网实例。
获取token的方法是getAuth
。我们的token是在Github配置生成,然后用户完整添加并存储在APP的里。所以不需要额外的API调用获取token。
getAuth: async ({ authState }): Promise => {
try {
if (!authState) {
const token = await AsyncStorage.getItem(GITHUB_TOKEN_KEY);
return { token } as AuthState; // *
}
return null;
} catch (e) {
return null;
}
},
在加星这一步可以看到,token是作为authState
对象的一个属性返回了。
使用token是通过方法addAuthToOperation
实现的。在这里最后会返回一个新建的operation。里面就存放了从getAuth
拿到的token:
addAuthToOperation: ({ authState, operation }) => {
// ...略
return makeOperation(operation.kind, operation, {
...operation.context,
fetchOptions: {
...fetchOptions,
headers: {
...fetchOptions.headers,
Authorization: `Bearer ${authState.token}`, // *
},
},
});
},
在加*这一步使用了token,从authState
里读出了token放在header的认证里随着api请求发送到了后端。
填充urql的client
通过Exchange配置好了token之后,关键的一步就完成了。接下来就需要把配置号的exchange和graphql的endpoint都添加到client里供graphql的查询使用。
const getGraphqlClient = () => {
const client = createClient({
url: 'https://api.github.com/graphql', // 1
exchanges: [
dedupExchange,
cacheExchange,
authExchange({ // 2
...authConfig,
}),
fetchExchange,
],
});
return client;
};
export { getGraphqlClient }; // 3
- 在url属性添加github的graphql的endpoint:
https://api.github.com/graphql
。 - 把auth exchange添加到exchange数组里。在这里配置的时候需要注意同步操作的exchange要放在异步操作的exchange前面。所以,
authExchange
要放在第三位。
实现一个查询
完成了上面的配置之后,我们可以开始实现一个简单的查询了。
在src/GithubScreen.tsx文件里可以看到具体的查询和执行后的效果。
首先来准备我们的查询语句。
import { gql, useQuery } from 'urql'; // 1
const REPO_QUERY = gql` // 2
query searchRepos($query: String!) {
search(query: $query, type: REPOSITORY, first: 50) {
repositoryCount
pageInfo {
endCursor
startCursor
}
edges {
node {
... on Repository {
name
}
}
}
}
}
`;
- 引入urql到工具方法gql和useQuery。useQuery后面会用到
- 编写查询语句。
这个查询语句看起来会让初学者不知所措。上面的例子我们也只是提到了query,metation之类少数几个关键字。那么这么长的(还不算长)查询语句如何能写出来呢。github专门提供了一个graphql api的explorer。点击这里到explorer。事实上,在很多语言对GraphQL的实现里都有这样一个explorer,至少是在开发阶段可以享受到这个服务。
- 首先在这个页面登录你的github账号。
- 在GraphiQL里就可以测试各种各样的查询语句了。
- 如果你有schema不清楚的可以看最右面的Doc文档
- 左下角的Query Variable可以输入查询的变量。这里就是query,对应的是repo的owner和repo的license类型。
- 中间一列就是查询的结果。
在中间看到查询结果之后,就可以判断你的查询语句是否合适。在本例中就是我们需要的查询语句了,直接复制到我们的代码里使用。
或者,如果你对于schema的定义略有了解的话,比如我们这次要查询的是repository。也可以使用查询语句编辑器里面的智能提示。整个来说,编写查询、修改语句是非常方便的。
一个简单的查询
上面说到如何编写一个查询语句,现在来使用这个语句查询repo列表。
urql也提供了这样的一个hook给react使用。
import { gql, useQuery } from 'urql'; // 1
const REPO_QUERY = gql `query searchRepos(...) { ... }`;
const GithubScreen = () => {
const [result] = useQuery({ // 2
query: REPO_QUERY,
variables: { query: 'user:facebook' },
});
const { data, fetching, error } = result; // 3
// ...略...
}
很简单一个简单的查询就可以搞定了。
- 只需要请
useQuery
出场。 - 使用useQuery。这个hook还会返回一个重新执行查询的方法,主要使给刷新时使用。
- 网络请求三状态,data是数据,fetching表示请求中,error是查询出现错误。
后面的代码可以根据出现的作出不同处理。
最后在FlatList中显示user是facebook的所有公开repo。
最后
一个简单的查询在这个app里就已经完成了。但是显然还有一些工作需要做。比如,loading和error处理都显得比较简陋。我们在前面的系列里提到了redux-toolkit。是否可以有一个slice来让这些逻辑的处理和redux结合在一起。
我们在依赖里也已经添加了react-native-paper
组件库。这个库可以在native和web上通用。我还没有把web断的截图放上来,主要因为有点惨不忍赌。UI也可以在后面稍作美化。
最主要的工作是如何在实际的开发中使用graphql。它的潜力绝不只是看起来很新颖这么简单看,而是可以实实在在的解决问题的。前端的同学会遇到一个最大的阻力就是来自于后端同学是否接收这一不太新的新事物。
在youtube上有一个30分钟搞定graphql的视屏,点这里可以看。实际后端要整合graphql肯定不会像视屏里的那么容易,而且他本身也仅仅演示了查询操作的处理。不过也不会像想象的那么难。GraphQL是一个标准,在实现上也是由从外部查询语句到内部获取数据之间的转换,也就是视频里的resolver,和schema定义。它依然依赖于底层的“DAO”层,或者是rest api的http请求。在GraphQL实现之后的收益就非常的显而易见,数据消费端(各种App)对于后端修改的需求会大幅度减少。摆弄query语句就可以何必后端新增API呢?