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来连接。
浙公网安备 33010602011771号