你其实不需要阿波罗
我第一次接触GraphQL 的时候,还只是个后端开发人员。大约两年前,得益于NestJS对 GraphQL 的支持,我获得了学习它的机会,并彻底爱上了它。GraphQL 几乎是自文档化的,而且只需一次请求就能获取所需的数据,这让我觉得,作为前端开发人员,使用 GraphQL API 会非常有趣。
通往前端 GraphQL 之路
我想运用这些新知识尝试做点前端项目,以便更好地了解它。那时我还在学习React 的前端开发(上次做前端的时候,jQuery 才是主流)。所以,很自然地,我用 DuckDuckGo 搜索了一下graphql react,第一个找到的就是Apollo。Apollo是个很棒的工具,功能丰富;你甚至可以用它管理整个应用程序的状态。但对于一个刚开始学习 GraphQL 的人来说,或者对于任何小型项目来说,它都显得有点复杂。我承认当时的想法很天真,但我真的觉得:哇,看来 GraphQL 只适合大型应用。尽管如此,我还是继续用 Apollo 做实验。不出所料,我花了很多时间学习如何使用 Apollo,这本身没什么不好,但对于任何学习者来说,这当然会让人感到畏惧。
大约去年,我发现了urql,它旨在成为 Apollo 的轻量级替代方案。我对此非常感兴趣,而且它的表现也确实很棒。更简洁的 API 和更少的功能意味着更少的文档编写时间,更多的时间可以用来实际构建项目。但就我的使用场景而言,它仍然感觉有点笨重。不过,如果现在要做一个重要的项目,我可能还是会选择 urql 而不是 Apollo,因为我觉得 Apollo 的功能太多了,不太符合我的需求。
虽然我还没有在专业领域使用过 GraphQL,但我一直在个人项目中使用它。不过,我始终觉得前端开发者学习 GraphQL 的入门门槛相当高。如果你用 DuckDuckGo(或者 Google)搜索,react graphql搜索结果最靠前的是 Apollo 和howtographql.com。如果你访问 howtographql.com,你会发现 Apollo 和 urql 都被列为初学者首选。我认为这人为地提高了初学者的入门门槛。
还有其他选择吗?
从浏览器向 GraphQL API 发送请求的最低要求是什么?其实很简单……只需要fetch API 即可。毕竟,你只需要向 GraphQL 端点发送一个 HTTP POST 请求。请求体中只需要包含一个字符串形式的查询/变更,以及查询所需的变量(可选)。请求体不一定是POST 请求,也可以指定application/graphqlMIME 类型;但为了简单起见,通常情况下,带有 MIMEapplication/json类型的 POST 请求都能正常工作。
fetch(`${API}/graphql`, {
method: 'post',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
query: `...`,
variables: {
...
},
})
}).then(r => r.json())
你可以通过类似这样的方式将其转换为更易于重用的函数:
async function gqlFetcher(query, variables) {
const { data, errors } = await fetch(`${API}/graphql`, {
method: 'post',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ query, variables }),
}).then(r => r.json())
if (errors) throw errors
return data
}
即使响应中包含错误,GraphQL 服务器也会返回 200 状态码,因此通常只需检查响应是否包含某个errors属性即可。但这仍然是一种过于乐观的处理方式,因为您没有考虑到其他类型的错误,例如可能返回 4xx 或 5xx 状态码的网络错误。为了本文的目的,我们暂且保留这种处理方式。
一些更符合人体工程学的设计
这种方法确实会让你失去 Apollo 和 urql 提供的友好接口。当然,你可以创建自己的钩子来提供更友好的接口;不过,我更喜欢使用 Vercel 的swr钩子。这个钩子适用于任何类型的远程数据获取;它的工作原理是先从缓存中返回数据,然后发送获取请求,最后返回新接收到的数据。它提供了一个友好的接口,让你可以在组件内部处理数据,同时保持 UI 的快速响应,正如他们所描述的那样。gqlFetcher我们之前创建的函数已经与这个useSWR钩子兼容,因此无需额外的工作。
import useSWR from 'swr'
const gqlQuery = `...`
function Component() {
// gqlFetcher is the same function we defined earlier
const { data, error } = useSWR(gqlQuery, gqlFetcher)
if (error) return <div>{/*...*/}</div> // JSX with error data
if (!data) return <div>Loading...</div> // Loading component
return <div>{/*...*/}</div> // JSX with returned data
}
为了向fetcher函数传递多个参数,swr hook 允许你将一个数组作为第一个参数传递。
const gqlQuery = `...`
const gqlVariables = {
// ...
}
function Component() {
const { data, error } = useSWR([gqlQuery, gqlVariables], gqlFetcher)
// ...
}
useSWR第一个参数用作key唯一标识请求的标识符。如果传递的是一个数组,swr则会对每个元素进行浅比较,如果其中任何元素发生更改,则会重新验证请求。
现有工具
如果你不想自己编写封装库fetch,可以使用graphql-request。它也是一个fetch用于发起 GraphQL 请求的 API 封装库,无需太多配置即可上手使用。它已经很好地处理了错误,并且默认是同构的(有些人可能不喜欢这一点)。GitHubswr页面上已经提供了一个使用示例。
import { request } from 'graphql-request'
const API = 'https://api.graph.cool/simple/v1/movies'
const fetcher = query => request(API, query)
function App () {
const { data, error } = useSWR(
`{
Movie(title: "Inception") {
releaseDate
actors {
name
}
}
}`,
fetcher
)
// ...
}
结论
感觉前端开发者想要进入 GraphQL 领域,门槛被人为地抬高了。Apollo 和 urql 被列为学习 GraphQL 的入门选择,这可能会让开发者误以为使用这类工具是使用 GraphQL API 的必要条件。但实际上并非如此;你只需要API 和一些其他的小型库来添加额外功能,就能fetch构建一个功能齐全的 Web 应用。我想不出有什么小型项目需要用到这些大型库提供的所有功能。需要澄清的是:我并不是说你不应该使用这些工具;我的意思是,你不必觉得它们是构建你想要的应用的必要条件。