在 2015 React 欧洲大会上,Lee Byron 介绍了 Facebook 的 GraphQL ,包含 GraphQL 背后的故事,查询语句的示例,还有核心的概念。GraphQL 非常易懂,直接看查询语句就能知道查询出来的数据是什么样的。你可以把 GraphQL 的查询语句想成是没有值,只有属性的对象,返回的结果就是对应的属性还有对应值的对象。
故事
从 2011 开始,Facebook 开始越来越重视移动端,一支很小的团队开始去做 Android 与 iOS 应用。Facebook 的强项是 Web,也非常的了解 Web ,而且在这方面储备了大量的技术。当年 Facebook 的主要平台就是传统的 浏览器 Web 服务器 数据服务 的组合,Web 服务器响应浏览器的请求,到数据服务那里提供出数据,然后再交给浏览器去显示。
他们打算尽可能的使用现有的代码去实施移动端的应用,所以一开始 Facebook 的移动应用就是一个浏览器,加上了一个本地的壳,内容基本上就是简单的定制以后的移动 Web 网站。这样的好处就是可以使用所有的现有的 Web 平台上的东西。这样工程师们也可以使用平时创建东西的方法。这种方法在短时间内也得到了很大的成功,并且让公司把重点放在移动端上。
一开始都还好,不过在移动应用上添加越来越多的功能以后,就有点吃力了,移动浏览器经常会消耗掉所有的内存,让应用崩溃。另一面,在 Web 上,Facebook 仍然快速的生成,而移动端有点跟不上脚步了。这让他们决定要去做真正的本地的移动应用。
2012 年开始,Facebook 要开始开发真正的本地应用。 这跟 Web 很不一样,所以开始重新思考应用的平台。Web 就是请求一个 URL ,返回一堆 HTML。而本地移动应用,为了给应用提供需要的数据,填充数据模型 ,显示视图,要想的问题是,怎么去请求,准备,传递这些数据。而当时 Facebook 现有的服务器主要功能还是只提供 HTML。
工程师们试了一些方法,比如 RESTful API,对于 Facebook 这种复杂的应用,可能需要定义很多的端点,不同的端点返回来的数据只是略有不同,造成了资源浪费,而且还需要大量的逻辑去处理这些数据。后来他们又试了 FQL, 这是 Facebook 的公共接口,应该是一种查询语言。功能很强大,而且返回来的数据也有很好的结构。不好的地方是,查询用的语言非常难理解,比如多个 JOIN ,主键什么的,所以经常会出错。
除了这些表面上遇到的问题,工程师们也非常不喜欢这些方法表达数据的形式,比如我们平时想像的数据并不是一大堆查询语言,LEFT JOIN,RIGHT JOIN .. 也不是资源的地址。而对象的形式非常适合表达数据,一个对象,里面有一些属性,不同的属性对应不同的值。几个工程师开始了现在的 GraphQL,一种用对象,属性,关系的,有点像图形的方式来表达想要的数据。
三年前,Facebook 用了 GraphQL 做了第一款真正的本地移动应用,现在,应用每天会接受 260 亿的请求。
查询示例
下面通过几个示例来看一下 GraphQL 的查询是什么样的。
{ me { name } }
上面这段就是一个 GraphQL 的查询语句,非常的容易懂。有点像 JSON 形式,比如一组大括号,里面是有点像对象的东西,这些叫做选择集,然后还有一些看起来像属性的东西,这些叫字段。不同的字段可以包含自己的选择集,这样可以去描述嵌套的查询。把这种形式的查询,用字符的形式,发送给 GraphQL 服务器,服务器解析,执行查询,然后返回来的东西是这样的:
{ "me": { "name": "wanghao" } }
返回来的数据应该就是 JSON。注意这里返回来的数据跟查询用的语言是非常像的,你会看到结构基本是一样的。这让 GraphQL 非常容易学习跟使用。用 GraphQL 的查询来描述要查询的结果数据的形状。在上面,name 是用户对象里的一个字段, me 有点像是一个起点,表示查询用户。
下面是另一种查询方法:
{ user(id: 6) { name } }
在上面这个查询里, id 是字段的一个参数,这些参数可以让查询返回不同的数据。我们可以把字段想成是一个可以返回数据的函数。再来看一个复杂点的查询:
{ me { name, profilePicture { width, height, url } } }
上面的查询要的是用户相关的数据,指定了 name 字段,也就是用户,还有 profilePicture 用户头像的字段,这个字段又包含了一个选择集,然后在这里又指定了需要 width 宽度,height 高度,还有 url 地址这几个字段。
返回来的结果像这样:
{ "me" { "name": "wanghao," "profilePicture": { "width": 50, "height": 50, "url": "https://cdn/some.jpg" } } }
用户头像可能会有多个尺寸,我们也可以查询特定尺寸的用户头像,方法就是指定一个参数还有对应的值,这里就是指定了一个 size 参数,值设置成了 300 :
{ me { name, profilePicture(size: 300) { width, height, url } } }
返回的结果像这样:
{ "me" { "name": "wanghao," "profilePicture": { "width": 300, "height": 300, "url": "https://cdn/300.jpg" } } }
在查询里面,可以多次查询同一个字段,然后使用别名,还有参数去影响返回的结果:
{ me { name, littlePic: profilePicture(size: 50) { width, height, url }, bigPic: profilePicture(size: 300) { width, height, url } } }
上面的查询时,两次查询 profilePicture 字段,不同指定了不同的别名,一个是 littlePic ,另一个是 bigPic,一个返回小图像,一个返回大图像。返回的结果看起来像这样:
{ "me" { "name": "wanghao," "littlePic": { "width": 50, "height": 50, "url": "https://cdn/50.jpg" }, "bigPic": { "width": 300, "height": 300, "url": "https://cdn/300.jpg" } } }
我们可以嵌套查询,比如查询用户的朋友,像这样:
{ me { name, friends { name } } }
上面指定了 friends 字段,这个字段有自己的选择集,就是一个 name 字段,返回的结果:
{ "me": { "name": "wanghao", "friends": [ { "name": "zhangsan" }, { "name": "lisi" } ] } }
friends 就是朋友的列表,返回来的就是一个数组,里面是一些对象,对象有一个 name 属性。嵌套可以是有任意的深度,比如不但要朋友列表的用户名,还需要朋友参加的活动的名字:
{ me { name, friends { name, events { name } } } }
返回的结果:
{ "me": { "name": "wanghao", "firends": [ { "name": "zhangsan", "events": [ { "name": "青岛啤酒节" } ] }, { "name": "lisi", "events": [ { "name": "青岛啤酒节" } ] } ] } }
在查询里也可以做一些限制,比如排序的标准,或者限制返回来的结果的数量:
{ me { name, friends(orderby: IMPORTANCE, first: 1) { name, events(first:1) { name } } } }
组合
GraphQL 可以组合使用查询。比如可以定义一种叫 fragment 的东西,就是查询片断,然后我们可以在不同的地方重复的去使用查询。比如下面的这个例子:
{ me { name friends { name events { name } } } }
可以转换成这样:
{ me { name friends { ...firendFragment } } } fragment friendFragment on User { name events { name } }
上面定义了一个叫 friendFragment 的查询片断,它返回用户朋友的名字,还有参加的活动的名字,然后我们可以在其它的查询里面使用这个查询片断。
在下面这个例子里,定义了 profilePicture 这个查询片断,然后我们在一个 React 的组件里用到了它。
fragment profilePicture on User { profilePicture { width, height, url } } class ProfilePicture extends React.Component { render() { var pic = this.props.data.profilePic; return <img src="https://ninghao.net/%7Bpic.url%7D" alt="" width="{pic.width}" height="{pic.height}" /> } }
这些查询片断也可以组合在一起使用,比如在下面的 PersonRow 这个查询片断里,用到了 profilePicture 这个查询片断。
fragment personRow on User { ...profilePicture, name, isFriend } class PersonRow extends React.Component { render() { var data = this.props.data; return ( <div> <ProfilePicture data={data} /> {data.name} {data.isFriend ? null : <AddFriendButton />} </div> ); } }
更多组合的例子:
{ event(id: 123) { ...attendeeList } } fragment attendeeList on Event { attendees { ...personRow } } fragment personRow on User { ...profilePicture, name, isFriend } fragment profilePicture on User { profilePicture { width, height, url } }
核心
GraphQL 语言的核心观念就是我们平时怎么去考虑数据, 几乎可以通过查询就知道查询的结果是什么样的。GraphQL 的核心是一个 Type System,用它去描述服务器的所有的功能。每一个级别的 GraphQL 查询,都会应用某个特定的 Type,这些 Type 描述了有哪一些字段是可用的,字段上能够使用的参数都有哪些,结果的类型是什么等等。下面是一个 Type 示例:
type Query { me: user user(id: Int): User } type User { name: string profilePicture(size: Int = 50): ProfilePicture friends(first: Int, orderby: FriendOrderEnum): [User] events(first: Int): [Event] } enum FriendOrderEnum { FIRST_NAME, LAST_NAME, IMPORTANCE } type ProfilePicture { width: Int height: Int url: String } type Event { name: String attendees(first: Int): [User] }
me 还有 user ,都会返回 User 这个 Type。上面我们用的是 me ,他会返回 User,这样我们就可以使用 User 这个 Type 里面描述的字段,比如名字,头像,好友列表等等。类型系统里不仅可以指定字段,还有字段可用的参数。比如 orderBy 就是一个参数,这个参数指定三种可能的值,这些值是用 FirendOrderEnum 描述的。
ProfilePicture 返回 ProfilePicture 这个 Type,width 与 height 这两个字段是整数类型的,url 是字符串类型的数据。如果字段返回的是一个列表,可以使用一组方框号,比如 friends 返回的是用户的列表,它的值就是 [User] 。
利用您现有的技术
GraphQL 允许你使用自己已有的技术平台,它并不关心你的后台到底用的是啥。GraphQL 并不是一个数据引擎,他不存储数据。我觉得有点像是一个查询解释的工具,你可以去定义一套 Type System ,创建一些 Type,描述一下服务器的功能,比如能够返回的数据,每个 Type 里的字段有点像是一个函数,你可以在里面定义返回的数据。
GraphQL
评论
不错支持,随便我还用不到
9 年 3 个月 以前
Facebook 的移动应用都是用这玩意提供数据。
9 年 3 个月 以前
好像明白,又好像糊涂了~
正在学你的 《Semantic UI 应用接口》,有点不关系。以前都一步一步照着照的~
这个视频第二节讲了准备。
学第三节课才发现自己没准备。哈哈~
9 年 3 个月 以前
GraphSQL
9 年 3 个月 以前
我读了一半,看到文章里有几处可以修改的地方:
1. "越来越重要移动端"应该是"越来越重视移动端"
2. 选择集里面的字段"firends"应该是"friends"
3. "然后我们可以在不同的地方重要的去使用查询"这样说"在选择集里,重要的地方可以使用fragment"是不是更好一点?
4. "还有参数的活动的名字"应该是"还有参加活动的名字"
8 年 1个月 以前
感谢啊 :)
8 年 1个月 以前
好文章就要帮助完美一点嘛:)
8 年 1个月 以前
有计划出一期关于 GraphQL 和 Relay 的教程吗?
7 年 7 个月 以前