🦄 2024 独立开发者训练营,一起创业!查看介绍 / 立即报名(剩余10个优惠名额) →

GraphQL 介绍

在 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

评论

不错支持,随便我还用不到

Facebook 的移动应用都是用这玩意提供数据。

好像明白,又好像糊涂了~
正在学你的 《Semantic UI 应用接口》,有点不关系。以前都一步一步照着照的~
这个视频第二节讲了准备。
学第三节课才发现自己没准备。哈哈~

GraphSQL

我读了一半,看到文章里有几处可以修改的地方:

1. "越来越重要移动端"应该是"越来越重视移动端"
2. 选择集里面的字段"firends"应该是"friends"
3. "然后我们可以在不同的地方重要的去使用查询"这样说"在选择集里,重要的地方可以使用fragment"是不是更好一点?
4. "还有参数的活动的名字"应该是"还有参加活动的名字"

感谢啊 :)

好文章就要帮助完美一点嘛:)

有计划出一期关于 GraphQL 和 Relay 的教程吗?

微信好友

用微信扫描二维码,
加我好友。

微信公众号

用微信扫描二维码,
订阅宁皓网公众号。

240746680

用 QQ 扫描二维码,
加入宁皓网 QQ 群。

统计

15260
分钟
0
你学会了
0%
完成

社会化网络

关于

微信订阅号

扫描微信二维码关注宁皓网,每天进步一点