GraphQL中数据类型分为标量类型和其他高级数据类型,标量类型即基础数据类型,包含Int、Float、String、Boolean、ID,其他高级数据类型包括Object(对象)、Interface(接口)、Union(联合类型)、Enum(枚举)、Input Object(输入类型)、List(列表)、Non-Null(非空类型),本文通过示例讲解接口、联合类型、输入类型的使用。
相关文章
- GraphQL学习:入门
- GraphQL学习:分页
- GraphQL学习:接口、联合类型、输入类型
目录
- 接口
- 联合类型
- 输入类型
接口
考虑如果在一个查询的返回结果中有两种对象,这两种对象有一部分字段是相同的,也有各自特有的字段。按照GraphQL中正常对象的写法,必须得写出查询所有需要返回的字段,这会导致返回的结果中有一些空值字段。在此情况下可引入接口类型,在接口中声明公用的字段,其他的对象可实现接口,再添加特有的字段。
在GraphQL中,接口的字段不能自动继承,需要在每个实现的接口中都声明接口中的字段,并且一个对象可以实现多个接口。
定义一个接口如下:
// 定义公用字段,因为每个接口都要声明公用字段,所以单独提取出来
const messageFields = {
id: { type: GraphQLInt },
type: { type: GraphQLString },
content: { type: GraphQLString },
createAt: { type: GraphQLString }
}
// 定义接口对象
const Message = new GraphQLInterfaceType({
name: 'Message',
fields: messageFields,
// 用于判断实际的数据是哪种实现类型
resolveType: (value) => {
if (value.type === 'user') return UserMessage
if (value.type === 'group') return GroupMessage
}
})
在根据实际的数据判断具体是哪个实现对象时,可以根据接口中声明的resolveType来判断,如果接口中不存在该方法,则根据实现该接口的对象中的isTypeOf方法来判断,如:
const UserMessage = new GraphQLObjectType({
name: 'UserMessage',
interfaces: [Message], // 可实现多个接口
fields: {
...messageFields,
userName: { type: GraphQLString }
},
// 判断数据是否为该对象类型,如果实现的接口中实现了resolveType方法,此处可以不写
isTypeOf: value => value.type === 'user'
})
const GroupMessage = new GraphQLObjectType({
name: 'GroupMessage',
interfaces: [Message], // 可实现多个接口
fields: {
...messageFields,
groupName: { type: GraphQLString }
}
})
构造查询方法和数据:
const initialDate = moment('2019-01-01')
const messages = Mock.mock({
'list|5': [{
'id|+1': 1000,
'type': /user|group/,
'content': /msg-[a-z]{5,10}/,
'createAt': () => initialDate
.add(Mock.Random.integer(1000, 10000), 'seconds')
.format('YYYY-MM-DD HH:mm:ss')
}]
}).list
const queryObjectType = new GraphQLObjectType({
name: 'RootQuery',
fields: {
messages: {
type: new GraphQLList(Message), // 此处使用接口数据类型
resolve (parent) {
return messages.map(one => {
if (one.type === 'group') {
return {
...one,
// 构造群组消息特有字段
groupName: 'group_' + Mock.Random.string(3)
}
}
if (one.type === 'user') {
return {
...one,
// 构造用户消息特有字段
userName: 'user_' + Mock.Random.string(5, 10)
}
}
return one
})
}
}
}
})
如果直接运行以上的代码,会出现如下错误:
由于GraphQL中不能根据对象声明所实现的interfaces来找到一个接口类型有哪些实现对象,因此还需要显示的指定有哪些可查找的对象类型,然后根据resolveType或者遍历指定可查找的对象的isTypeOf来获取数据对应的实现对象。指定对象类型如下:
const schema = new GraphQLSchema({
types: [Message, UserMessage, GroupMessage],
query: queryObjectType
})
以上已经实现了服务端的接口类型的示例,接下来需要构造GraphQL查询语句来查询接口类型的数据。
如果只需要返回接口公用的数据,查询语句如下:
{
messages {
id
type
content
createAt
# 接口类型中默认会添加上__typename字段,表示最终实现接口的对象类型
__typename
}
}
查询结果如下:
当需要返回接口实现对象的特殊字段时,需要使用... on
的语法,表示如果返回对象为该类型时,取该类型中的特定字段。查询语句示例如下:
{
messages {
id
type
content
createAt
... on UserMessage {
userName
}
... on GroupMessage {
groupName
}
}
}
查询结果如下:
联合类型
有时候在一个查询的结果里返回的类型可能是两种不同的对象,在指定返回字段时应根据实际的类型来指定返回哪些字段,这时可以使用联合类型。
首先定义两种对象类型,它们可以有相同的字段,也可以没有。如果相同字段过多其实就是接口类型的使用场景了。
// 这里同样以消息对象为例
// 虽然它们有公共字段,但可以不使用接口类型处理
const messageFields = {
id: { type: GraphQLInt },
type: { type: GraphQLString },
content: { type: GraphQLString },
createAt: { type: GraphQLString }
}
const UserMessage = new GraphQLObjectType({
name: 'UserMessage',
fields: {
...messageFields,
userName: { type: GraphQLString }
}
})
const GroupMessage = new GraphQLObjectType({
name: 'GroupMessage',
fields: {
...messageFields,
groupName: { type: GraphQLString }
}
})
以上文定义的UserMessage
和GroupMessage
对象,定义一个联合类型的对象如下:
const Message = new GraphQLUnionType({
name: 'Message',
types: [UserMessage, GroupMessage],
// 与接口类型一样,用于判断实际的数据是哪种类型
resolveType: (value) => {
if (value.type === 'user') return UserMessage
if (value.type === 'group') return GroupMessage
}
})
这里的resolveType使用和接口类型完全一致,如果联合类型中不存在该方法,则根据联合类型中指定的对象的isTypeOf方法来判断。
构造查询方法和数据与上文接口类型中的方法完全一致,最后同样需要在schema中指定联合查询中使用了哪些对象,如下:
const schema = new GraphQLSchema({
types: [Message, UserMessage, GroupMessage],
query: queryObjectType
})
查询联合类型的对象时,除了没有公共字段外,其他与接口类型的查询一致,也是使用... on
语法来查询,查询语句如下:
{
messages {
...on UserMessage {
id
userName
content
}
...on GroupMessage {
id
groupName
content
}
}
}
查询结果如下:
接口类型和联合类型在使用体验上基本相同,通常是看公共字段的多少来决定是否使用接口提取公共字段。
输入类型
在之前的查询参数中,基本上都是使用的标量类型,如果要表示查询参数是对象类型,需要使用输入类型。
定义一个输入类型如下:
const UserInput = new GraphQLInputObjectType({
name: 'UserInput',
fields: {
id: { type: GraphQLInt },
name: { type: GraphQLString },
age: { type: GraphQLInt },
labels: { type: GraphQLString }
}
})
输入类型和定义一个对象类型是一致的,不过输入类型可设置默认值,如下:
const PaginationInput = new GraphQLInputObjectType({
name: 'PaginationInput',
fields: {
page: { type: GraphQLInt, defaultValue: 1 },
pageSize: { type: GraphQLInt, defaultValue: 3 }
}
})
使用上面定义的两个输入类型作为查询参数的类型,示例如下:
const users = Mock.mock({
'list|100': [{
'id|+1': 1000,
'name': /user-[a-zA-Z]{4}/,
'age|1-100': 100,
'labels': () => ['sportsman', 'programmer', 'teacher', 'musician', 'chef'].filter(() => Math.random() < 0.3)
}]
}).list
const User = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: GraphQLInt },
name: { type: GraphQLString },
age: { type: GraphQLInt },
labels: { type: new GraphQLList(GraphQLString) }
}
})
const UserPagination = new GraphQLObjectType({
name: 'UserPagination',
fields: {
data: { type: new GraphQLList(User) },
totalCount: { type: GraphQLInt },
totalPageCount: { type: GraphQLInt }
}
})
// 查询的GraphQL对象类型
const queryObjectType = new GraphQLObjectType({
name: 'RootQuery',
fields: {
users: { // 条件查询
type: UserPagination,
args: {
user: { type: UserInput }, // 输入类型
pagination: { type: PaginationInput } // 输入类型
},
resolve (parent, { user: userInput, pagination }) {
const rules = Object.keys(userInput).map(one => {
if (one === 'labels') return user => user[one].includes(userInput[one])
if (one === 'name') return user => user[one].indexOf(userInput[one]) > -1
return user => user[one] === userInput[one]
})
const { page, pageSize } = pagination
const totalData = users.filter(one => rules.every(rule => rule(one)))
const data = totalData.slice((page - 1) * pageSize, page * pageSize)
const { length: total } = totalData
return {
data,
totalCount: total,
totalPageCount: Math.ceil(total / pageSize)
}
}
}
}
})
构造查询语句,查询标签中包含"teacher",查询结果为第二页的数据,如下:
{
users(user: {labels: "teacher"}, pagination: {page: 2}) {
data {
id
name
age
labels
}
totalCount
totalPageCount
}
}
查询结果如下:
总结
本文主要介绍了接口、联合类型、输入类型这三种高级数据类型的使用,使用起来还是比较简单的,不过由于官方文档中没有实际的示例演示,于是通过编写示例的方式加强对GraphQL中数据类型的使用和理解。
到目前为止,学习的GraphQL都是比较基础的内容,不过也算初步入门。其中最重要的收获应该是对GraphQL有了比较清晰的认识,为以后的技术选型打下基础。在实际项目中使用GraphQL通常也是使用社区里的GraphQL框架来简化开发,如Apollo、Relay。
本文参考资源如下:
- https://graphql.cn/learn/schema/
- https://graphql.cn/graphql-js/type/
- http://taobaofed.org/blog/2016/03/10/graphql-in-depth/