fullstack react 学习笔记 使用GraphQL

1、第一个GraphQL query

发送GraphQL query和请求API一样都是向HTTP端口发送请求,不同的是GraphQL服务器只有一个端口处理所有的GraphQL请求。

示例地址:https://www.graphqlhub.com/

示例:

curl -H 'Content-Type:application/graphql' -X POST https://www.graphqlhub.com/graphql?pretty=true -d '{ hn { topStories(limit: 2) { title url } } }'

  -H 'Content-Type:application/graphql':向服务器说明发送的是graphQL请求。

  /graphql是路径地址

  参数pretty是告诉服务器以人可读的方式返回数据。

  -d:是一个graphQL请求。把它展开为:

{
  hn{
    topStories(limit:2){
      title
      url
    }
  }
}

  可以考到的返回结果:

{
  "data": {
    "hn": {
      "topStories": [
        {
          "title": "Energy Department teams up with Bill Gates to move mini-nuclear plants to market",
          "url": "https://www.washingtonexaminer.com/policy/energy/energy-department-teams-up-with-bill-gates-to-move-mini-nuclear-plants-to-market"
        },
        {
          "title": "Set Theory and Algebra in CS: Introduction to Mathematical Modeling (2013) [pdf]",
          "url": "https://pdfs.semanticscholar.org/d106/6b6de601c1d7d5af25af3f7091bc7ad3ad51.pdf"
        }
      ]
    }
  }
}

  可以看到返回的JSON数据结构与请求的结构一模一样。这就是graphQL的特点:服务器你仅会返回请求相关的数据,不多不少。

表面上看请求仅仅发送了请求的字段,其实它还发送了字段的类型。GraphQL服务器指导topStories的类型是list,title和url的类型是string。

2、使用GraphQL的好处

(1)请求变得容易理解。

(2)便于单元测试和集成测试。

(3)性能表现更加优越,尤其在手机端。

(4)良好的开发体验。

(5)和React良好的集成。

3、比较GraphQL和REST

REST每个端口提供固定的数据,如果需要的数据不在端口,需要新增或修改端口,有可能需要的数据需要多个端口返回,也可能一个端口返回的数据已经超过了需求。数据或多或少,一般无法正合适的满足需求。还有REST调试比较麻烦,而GraphQL类型系统带有内省功能(就是可以使用GraphQL发现GraphQL服务器端的信息)。

4、比较GraphQL和SQL

在服务器端执行SQL查询关系型数据库是非常棒的,但是在前端使用SQL并不友好。另外直接从客户端发送SQL到数据库也存在着安全隐患,GraphQL会比原生的SQL要安全一些。

使用GraphQL并不是意味着放弃SQL,GraphQL是可以抽象多种形式的数据库,包括sql,redis,甚至api。GraphQL可以作为客户端与服务器数据库中间的一个桥梁。

5、Relay和GraphQL框架

Relay是一个框架连接React component和GraphQL server.

class Item extends React.Component { 
render() {
 let item = this.props.item; 
 return ( 
 <div> 
 <h1><a href={item.url}>{item.title}</a></h1> 
 <h2>{item.score} - {item.by.id}</h2> 
 <hr /> 
 </div> 
); 
 } 
 }; 

Item = Relay.createContainer(Item, { 
fragments: { 
 item: () => Relay.QL` 
 fragment on HackerNewsItem { 
 id 
title, 
 score, 
 url 
by { 
id 
 } 
} 
 ` 
 }, 
});

  Item组件可以自动从GraphQL server端获取数据。背后其实是Relay负责一系列的网络请求和缓存。还有另一个相同的框架Apollo。

5、概述

GraphQL包含两部分,一部分是前端的GraphQL,另一部分是后端的GraphQL,后端可以使用多种编程语言来编写

我们将使用IDE GraphiQL来学习前端GraphQL请求。GraphiQL是facebook开发的可以寄存在任意的GraphQL server上。GraphiQL可以帮助检查错误,并且可以自动提示功能。GraphiQL指导有什么type和field,并且支持文档查询。

6、GraphQL语法

查看GraphQL规范https://facebook.github.io/graphql/

专用术语:

document:发送给服务器端的所有的string。

operations:一个document可以有一个或者多个操作,操作包括query,mutation和subscribe.

query:是一种操作,向服务器请求数据。

mutation:是修改数据后并跟一个获取数据的请求。

我们看一下query 操作

query getTwoStories{
  hn{
    topStories(limit:2){
      title
      url
    }
  }
}

  query代表操作类型,getTwoStories代表了操作名称。如果只有一个query请求,可以省略query和操作名。最好是加上,这样便于调试。如果有多个操作的话,必须为每个操作加上操作类型和操作名称。通常我们不会发送多个操作,因为GraphQL server一次只能处理一个操作。

让我们来仔细看一下query操作,

selections:query包含一系列的selections,通常是fields

field:代表了一些数据,可以说单一类型的数据scalar,也可以是复合类型complex的数据(包含单一类型的数据和复合类型的数据),在上面的例子中hn,topStories,title,url都是field,其中title和url是单一类型(string)的数据,而topStories和hn是复核类型的数据。

  GraphQL请求的一个特点是,请求的最底层必须是单一类型的数据。否则的话会报错。GraphQL的哲学理念就是你必须精确的请求,服务器才能精确的返回你所需要的数据。

scalar:单一类型的数据包括Int,String ,Float,ID,Boolean.

complex:包括Object,Interface,Union,Enum,List

  我们可以任意的组织单一类型和复核类型以便组成更加复杂的类型。

arguments:fields可以有参数。你可以把fields视作function。在上例中,limit就是twoStories的参数,参数也是有类型的,如果上例中你给limit参数设置成字符串“10”就会报错。参数也可以是复合类型,例如input objects.

input objects:输入对象值是用大括号括起来的键控输入值的无序列表.

示例:

{
  hn{
    createStories(storyData:{url:"http://fullstackexample.com"}){
      url
    }
  }
}

  

schema:GraphQL server端所有fields的集合称为schema.GraphiQL可以下载服务器的schema并且勇于自动完成和类型检查等功能。

7、复合类型

在GraphiQL中我们可以查看不同field的类型。

(1)Unions:

一般我们会明确定义一个field是一种类型(单一的或复合的)。但是GraphQL给了我们一种机制,可以为字段定义成为一个联合类型,是A类型或者B类型,不确定。

示例:

union SearchResult = Photo | Person 
 type Person { 
 name: String 
 age: Int 
 } 
 type Photo { 
 height: Int 
 width: Int 
 } 
 type SearchQuery { 
 firstSearchResult: SearchResult 
 }

  可以看到SearchQuery type有一个字段firstSearchResult就是一个unions field。可能是Person type或者是Photo type.

(2)Fragments:

可以看到上面的联合类型返回的是Person或者Photo,两者没有共同的字段,如果我们最末级字段要返回height应该怎么写请求呢?折旧是Fragment的作用。

示例:

 { 
 firstSearchResult { 
 ... on Person { 
 name 
 } 
 ... on Photo { 
 height 
} 
 } 
}

  这个...on Person 就是一个行内的fragment.上述代码的意思就是,如果类型是Person就返回name,如果类型是Photo就返回height.因此...可以理解为if elif 的意思。

Fragments不仅可以写在行内,也可以通过命名后写成一个模块,用在任何地方,示例:

{ 
 firstSearchResult { 
 ... searchPerson 
 ... searchPhoto 
 } 
 } 
 fragment searchPerson on Person { 
 name 
 } 
fragment searchPhoto on Photo { 
 height 
}

  (3)Interfaces

一个type可以实施(implement)一个或者多个接口(interface),实施接口的type必须具有interface的field,此外还可以定义自己的field.

示例:

interface Searchable { 
 searchResultPreview: String 
} 
 type Person implements Searchable { 
 searchResultPreview: String 
 name: String 
 age: Int 
 } 
 type Photo implements Searchable { 
 searchResultPreview: String 
 height: Int 
 width: Int 
 } 
 type SearchQuery { 
 firstSearchResult: Searchable 
 }

  现在firstSearchResult字段是一个Searchable类型,因此现在可以返回任意实施了接口的类型,包括Person和Photo.对于接口类型,我们也需要使用Fragment以便返回对应的最末级field.

8、看看Graph

Graph是相关联的一组对象的组合。每个对象被称为一个节点(node),对象之间的链接被称为(edge)

并不是你的产品数据非常像graph,你就必须使用图数据库,facebook后台大部分使用的还是mysql数据库。任何的数据库,在GraphQL下都会被转换为Graph.

理想的shcema应该与应该与数据结构图一致。类似的graph如下所实话:

{
  viewer{
    id 
    name
    likes{
      edgs{
        cursor
        node{
          id
          name
        }
      }
    }
    node(id:'123123"){
      id
      name
    }
  }
}

  我们增加了很多的node,edgs等字段,来描述graph.

9、Graph Nodes

当我们查询图的时候,我们通常会从一个节点开始,然后直接从该节点获取数据,或者从与该节点相连的节点上获取数据。

习惯上我们通常会先定义一个节点接口:

interface Node{
    id:ID
}

  ID是一个scalar类型,从string转义而来。GraphQL与其他协议如REST的一个重要区别是:在GraphQL当中,所有node的id 在全局是惟一的。如果你有一个User的id是1,同时Photo也有一个id是1,这样在GraphQL中便会造成冲突。使用单一id的原因是你应该可以让你的query在最顶层字段可以通过id查询任意的节点。

在关系型数据库中,我们通常只能保证单个table的id是唯一的。因此我们需要为每个id增加一个对应的类型前缀保证id在全局是唯一的。

为什么我们需要通过node(id:)来查找任意节点的能力呢?这样可以让我们更新数据或查询数据而不需要指导数据来自shecema的那一部分。

通常我们不会一开始就通过一个全局的id来查询节点,而是用Viewer

10、Viewer

除了node(id:)field,一般顶层还有一个viewer field。viewer代表了当前用户和所有与当前用户的关联节点。在shcema层级中,viewer type和其他type一样都Implement node type.

{
  viewer{
    id#当前用户
    messages{ #当前用户的消息
      paticpants{
        id
        name
      }
    }
    channels{#当前用户的频道
      name
      unreadCount
    }
  }
}

  如果没有Viewer,我们必须请求两次,第一次请求当前用户的id,第二次根据id请求当前用户的messages.

  viewer在控制授权时也非常有帮助。

11、graph connections and edge

在上面的例子中我们有两个connections(messages和channels),对于简单的应用我们可以返回一组列表,但是当数据量非常大,返回一个列表将变得不现实。通常我们会分页pagination;简单的分页对于固定数据是非常好的,但是对于实时更新变现就会变差。

GraphQL的解决办法是cursor_based_pagination。GraphQL要求通过传递cursors来明确节点在列表中的位置以便于返回cursor后面的数据。具体实现:GraphQL首先请求一组节点,这一组节点中的每个节点都有一个唯一的cursor,当需要加载更多数据的时候,他将要发送一个新的请求:“给我XYZ后的10个节点”

让我们举例来看一下:

{
  hn2 {
    nodeFromHnId(id:"clayallsopp", isUserId:true) {
      id
      ... on HackerNewsV2User {
        submitted(first: 2) {
          pageInfo {
            hasNextPage
       startCursor endCursor } edges { cursor node { id ... on HackerNewsV2Story { score url } ... on HackerNewsV2Comment { text } } } } } } } }

  首先我们通过id获取初始节点nodeFromHnId.

  第二步:我们获取两个节点在submitted connection上。一个connection有两个field(pageInfo,和edgs),pageInfo是一个元数据关于当前页。edgs是一组真实的节点,每个在edgs内部的实体都有一个cursor和对应的node.

在这里cursor和id是两个独立的字段,cursor通常是暂时的,只存在于某个时间段。

返回结果:

{
  "data": {
    "hn2": {
      "nodeFromHnId": {
        "id": "SGFja2VyTmV3c1YyVXNlcjpjbGF5YWxsc29wcA==",
        "submitted": {
          "pageInfo": {
            "hasNextPage": true,
            "startCursor": "YXJyYXljb25uZWN0aW9uOjA=",
            "endCursor": "YXJyYXljb25uZWN0aW9uOjE="
          },
          "edges": [
            {
              "cursor": "YXJyYXljb25uZWN0aW9uOjA=",
              "node": {
                "id": "aXRlbToxNjAzMzgxNA==",
                "score": 2,
                "url": "https://lethain.com/things-learned-in-2017/"
              }
            },
            {
              "cursor": "YXJyYXljb25uZWN0aW9uOjE=",
              "node": {
                "id": "aXRlbToxNTk1NTk3NA==",
                "score": 5,
                "url": "https://pitchfork.com/features/article/is-secretive-virtual-reality-startup-magic-leap-dreaming-up-the-future-of-music/"
              }
            }
          ]
        }
      }
    }
  }
}

  我们的前端可以使用最后一个cursor:"YXJyYXljb25uZWN0aW9uOjE=",结合after参数构造一个请求获取后面的节点。相关的参数有before,after,first,last,参数后面都跟整数。

 12、mutations

mutations可以在修改数据同时请求返回数据,这是GranphQL的一大特色。

mutation 操作同query操作一样,都是由字段,参数等组成。

13、subscription

subscription的主要作用是实时更新。服务端应该提供订阅的功能,客户端可以选择是否订阅他们。

14、GraphQL with javascript

使用fetch发送GraphQL请求。

 var query = ' { graphQLHub } '; 
 var options = { 
 method: 'POST', 
 body: query, 
 headers: { 
 'content-type': 'application/graphql' 
 } 
 }; 
 fetch('https://graphqlhub.com/graphql', options).then((res) => { 
return res.json(); 
}).then((data) => { 
 console.log(JSON.stringify(data, null, 
)); 
 });

  15、GraphQL with react

使用relay或者apollo来连接。

 

posted @ 2018-08-26 12:28  tutu_python  阅读(419)  评论(0)    收藏  举报