React全家桶:react-starter-kit之三

之前

在之前写分页功能时候,曾经添加过一个count的小接口,可以通过GraphQL来查询news的条目数。这里我们就以此来实例说明如何使用GraphQL来做一个接受一个查询。

这个接口具备两种逻辑处理。

  1. 当没有传递指定标签id时候返回全部条目
  2. 指定标签id时候返回指定id的条目数

过程

类型系统

在进行查询之前我们需要对字段类型进行约束。
文件: data/types/NewsCountType.js

1
2
3
4
5
6
7
8
9
10
11
12
13
import {
GraphQLObjectType as ObjectType,
GraphQLInt as IntType,
} from 'graphql';

const NewsCountType = new ObjectType({
name: 'NewsCount',
fields: {
count:{type:IntType}
},
});

export default NewsCountType;

这里使用GraphQLObjectType来定义了返回的是一个对象,GraphQLInt则定义了返回的count属性是一个数字。
当然,这里只是非常的简单了使用了一个小案例。这里列举一下更多的类型定义。

  • GraphQLScalarType:Scalar Types,标量类型。最细粒度的类型
    • GraphQLInt:Int
    • GraphQLFloat:Float
    • GraphQLString:String
    • GraphQLBoolean:Boolean
    • GraphQLID:ID
  • GraphQLObjectType:对象
  • GraphQLInterfaceType:接口
  • GraphQLUnionType:联合
  • GraphQLEnumType:枚举
  • GraphQLInputObjectType:输入对象
  • GraphQLList:列表
  • GraphQLNonNull:NonNull

GraphQLObjectType

几乎所有的GraphQL都是Object对象。它具有一个name属性和一个非常重要的fields属性,这里定义了它的数据结构。就像之前,贴出的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var AddressType = new GraphQLObjectType({
name: 'Address',
fields: {
street: { type: GraphQLString },
number: { type: GraphQLInt },
formatted: {
type: GraphQLString,
resolve(obj) {
return obj.number + ' ' + obj.street
}
}
}
});

var PersonType = new GraphQLObjectType({
name: 'Person',
fields: () => ({
name: { type: GraphQLString },
bestFriend: { type: PersonType },
})
});

GraphQLInterfaceType

(这块暂时不是太理解,见谅)
When a field can return one of a heterogeneous set of types, a Interface type is used to describe what types are possible, what fields are in common across all types, as well as a function to determine which type is actually used when the field is resolved.

1
2
3
4
5
6
var EntityType = new GraphQLInterfaceType({
name: 'Entity',
fields: {
name: { type: GraphQLString }
}
});

GraphQLUnionType

当一个字段可以返回一个异构集合的类型中的一种,一个Union类型用于描述什么类型是可能的,以及提供一个函数,以确定哪些类型时该字段被解析实际使用。

1
2
3
4
5
6
7
8
9
10
11
12
var PetType = new GraphQLUnionType({
name: 'Pet',
types: [ DogType, CatType ],
resolveType(value) {
if (value instanceof Dog) {
return DogType;
}
if (value instanceof Cat) {
return CatType;
}
}
});

GraphQLEnumType

可枚举的数据类型

1
2
3
4
5
6
7
8
var RGBType = new GraphQLEnumType({
name: 'RGB',
values: {
RED: { value: 0 },
GREEN: { value: 1 },
BLUE: { value: 2 }
}
});

GraphQLInputObjectType

为了查询对象定义的数据类型.

1
2
3
4
5
6
7
8
var GeoPoint = new GraphQLInputObjectType({
name: 'GeoPoint',
fields: {
lat: { type: new GraphQLNonNull(GraphQLFloat) },
lon: { type: new GraphQLNonNull(GraphQLFloat) },
alt: { type: GraphQLFloat, defaultValue: 0 },
}
});

GraphQLList

列表是其他类型的封装,通常用于对象字段的描述。

1
2
3
4
5
6
7
const PersonType = new GraphQLObjectType({
name: 'Person',
fields: () => ({
parents: { type: new GraphQLList(Person) },
children: { type: new GraphQLList(Person) },
})
});

GraphQLNonNull

Non-Null 强制类型的值不能为 null.在请求出错时会报错。

小结

类型系统是GraphQL重要的组成部分之一,它规定了数据的类型。如果数据不符合指定的类型,GraphQL将会自动进行报错。这对调试接口非常重要。同时可以强制接口符合约定,保证代码的健壮性。

查询

GraphQL的查询语句相信是GraphQL最吸引人的地方,我个人认为,没有之一。
这里简单记录一下自己近段时间做的东西。主要包含前端和后端的实现,没有用到的地方就暂时不记录了。
这是获取数据的代码部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
resetNewsState:function (context,currentPage,pageSize,hotTag) {
const self = this;
//获取新的数据
fetch('/graphql', {
method: 'post',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: `{news( currentPage:${currentPage||this.props.currentPage}, pageSize:${pageSize||this.props.pageSize}, hotTag:${hotTag||9999} ){title,link,contentSnippet}, tags{name,id}, count( hotTag:${hotTag||9999} ){count}}`
}),
credentials: 'include'
}).then(async function (resp) {
let { data } = await resp.json();
//设置全局store提供给页码组件
//总条目数
self.props.setRuntime({
name:"newsTotalItem",
value:data.count.count
});


//内部状态
(context||this).setState({
news:data.news,
tags:data.tags,
newsLoading:false
});
});
},

其中 query就是其中关键的查询代码。GraphQL的查询代码个人认为如果做好了配置,那么几乎是所见即所得的。如果这样还不太好理解。那么我就贴一下请求参数的反悔响应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//request
"{
news(
currentPage:1,
pageSize:10,
hotTag:9999
){title,link,contentSnippet},
tags{name,id},
count(
hotTag:9999
){count}}"
//response
{
"data": {
"news": [
{
"title": "测试标题1",
"link": "http://www.baidu.com",
"contentSnippet": "测试摘要测试摘要"
},
{
"title": "测试标题2",
"link": "http://www.baidu.com",
"contentSnippet": "测试摘要测试摘要"
},
],
"tags": [
{
"name": "javascript",
"id": "1"
},
{
"name": "css",
"id": "2"
}
],
"count": {
"count": 17
}
}
}

是不是很容易理解呢?通过编写查询语句,几乎就可以前端自己设计好需要的需要的json数据,从而优化请求。当然,这紧紧是前端部分。当我们使用以下代码查询条目数的时候,指定了hotTag的id为9999(这里是一个特殊标志符,代表查询全部hotTag)。

1
2
3
count(
hotTag:9999
){count}}

其中, count:{type:IntType}规定了hotTag参数必须为数字。这个在第一个代码片段有写。但是这紧紧是前端的查询,为了获取到想要的数据,我们还需要后端也做好相应的查询操作。

以下贴出查询的完整查询代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
//=============================
//src/data/queries/newsCount.js
//=============================

import { GraphQLInt } from 'graphql';
import fetch from '../../core/fetch';
import NewsCountType from '../types/NewsCountType';

// React.js News Feed (RSS)
const url = '/api/news';

let count = 0;
let lastFetchTask;

const newsCount = {
type: NewsCountType,
args: {
hotTag:{ type: GraphQLInt}
},
async resolve(reqObj,{hotTag}) {
lastFetchTask = await fetch(url,{
method: 'post',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({hotTag}),
credentials: 'include',
})
.then(response => response.json())
.then(data => {
if (data.code === 200) {
count = data.count;
}

return {count};
})
.finally(() => {
lastFetchTask = null;
});

if (count.length) {
return {count};
}

return lastFetchTask;
},
};

export default newsCount;

//===================
//src/data/schema.js
//===================

import {
GraphQLSchema as Schema,
GraphQLObjectType as ObjectType,
} from 'graphql';

import tags from './queries/tags';
import content from './queries/content';
import news from './queries/news';
import count from './queries/newsCount';

const schema = new Schema({
query: new ObjectType({
name: 'Query',
fields: {
tags,
content,
news,
count
},
}),
});

export default schema;

src/data/queries/newsCount.js文件是对数据进行具体查询和定义参数的文件,而src/data/schema.js则导出了一个Schema。

在这里我们可以发散一下,当我们要使用和定义后端的GraphQL相关,那么该怎么做呢:
我们需要一个GraphGL的Schema。它有以下结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const schema = new Schema({
query: new ObjectType({
name: 'Query',
fields: {
count:{
type: NewsCountType,
args: {
hotTag:{ type: GraphQLInt}
},
resolve:function(reqObj,paramObj){

}
}
},
}),
});

结合我们的类型系统,是不是很有感觉了呢?是的,当我们反复循环嵌套相关的数据类型,然后使用Schema返回。那么就可以配合前两篇文章写到的。从前端后端两个方面来实现GraphQL的对接。

收个尾

写到这里心里其实有些索然。因为感觉没有说到很深入的境地。但是怎么说呢,通过这篇文章好好体会一下,应该也可以在react-starter-kit里面使用GraphQL了,毕竟正常使用应该也用不到太过复杂的功能。

然而毕竟事情未能尽善,从第一篇到这一篇,期间磨磨蹭蹭一个月也过去了,新东西太多太快但是时间精力有限。限于个人水平文章也无法写的很通俗,但是确实是用心去做了。

有时间把源代码整理完毕一起放出来供参考。