分頁是十分常見的接口使用場景,該篇文章詳細介紹常見的分頁方式,主要關注于GraphQL中分頁的形式。
相關文章
- GraphQL學習:入門
- GraphQL學習:分頁
- GraphQL學習:接口、聯(lián)合類型、輸入類型
目錄
- 基于偏移量分頁
- 基于游標分頁
- Relay風格的分頁
- 自定義分頁格式
基于偏移量分頁
查詢參數(shù):
-
offset: 指定數(shù)據(jù)從第幾個開始 -
limit: 指定實際返回的數(shù)據(jù)個數(shù)
基于偏移量的分頁實現(xiàn)非常簡單,但是如果數(shù)據(jù)發(fā)生變化,前后兩頁查詢可能查出相同的數(shù)據(jù)。實現(xiàn)如下:
// mock數(shù)據(jù)
const users = Mock.mock({
'list|100': [{
'id|+1': 1000,
'name': /user-[a-zA-Z]{4}/
}]
}).list
// 定義User類型
const User = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: GraphQLInt },
name: { type: GraphQLString }
}
})
const queryObjectType = new GraphQLObjectType({
name: 'RootQuery',
fields: {
users: {
type: new GraphQLList(User),
args: {
offset: { type: GraphQLInt }, // 偏移量
limit: { type: GraphQLInt } // 返回的數(shù)據(jù)個數(shù)
},
resolve (parent, { offset, limit }) {
return users.slice(offset, offset + limit)
}
}
}
})
查詢前5條數(shù)據(jù),如下:

從第5條數(shù)據(jù)開始查詢5條數(shù)據(jù),如下:

基于偏移量的分頁通常直接返回數(shù)組數(shù)據(jù),由于實現(xiàn)方法簡單,適用于數(shù)據(jù)變化較小,不需要顯示分頁信息的場景。如評論區(qū)的歷史評論,每次只需要基于上次的偏移量,往后加載一定數(shù)量的數(shù)據(jù)。
基于游標分頁
查詢參數(shù):
-
cursor: 當前游標 -
limit: 指定實際返回的數(shù)據(jù)個數(shù)
基于游標分頁會返回游標之后的數(shù)據(jù),所以需要數(shù)據(jù)有明確且固定的排序規(guī)則,比如遞增的id、遞增的創(chuàng)建時間等。通常返回的數(shù)據(jù)需要指明游標,以便于下一次使用新的游標查詢。
使用id作為游標
const users = Mock.mock({
'list|100': [{
'id|+1': 1000,
'name': /user-[a-zA-Z]{4}/
}]
}).list
const User = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: GraphQLInt },
name: { type: GraphQLString }
}
})
const queryObjectType = new GraphQLObjectType({
name: 'RootQuery',
fields: {
users: {
type: new GraphQLList(User),
args: {
cursor: { type: GraphQLInt }, // id游標
limit: { type: GraphQLInt }
},
resolve (parent, { cursor, limit }) {
const offset = findIndex(users, one => one.id === cursor)
return users.slice(offset + 1, offset + 1 + limit)
}
}
}
})
查詢id為1003的用戶后5條數(shù)據(jù),如下:

使用createAt作為游標
const initialDate = moment('2019-01-01')
// mock生成遞增的時間數(shù)據(jù)
const users = Mock.mock({
'list|100': [{
'id|+1': /[a-zA-Z0-9]{10}/,
'name': /user-[a-zA-Z]{4}/,
'createAt': () => initialDate
.add(Mock.Random.integer(1000, 10000), 'seconds')
.format('YYYY-MM-DD HH:mm:ss')
}]
}).list
const User = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: GraphQLString },
name: { type: GraphQLString },
createAt: { type: GraphQLString }
}
})
const queryObjectType = new GraphQLObjectType({
name: 'RootQuery',
fields: {
users: {
type: new GraphQLList(User),
args: {
cursor: { type: GraphQLString }, // createAt游標
limit: { type: GraphQLInt }
},
resolve (parent, { cursor, limit }) {
const offset = findIndex(users, one => one.createAt === cursor)
return users.slice(offset + 1, offset + 1 + limit)
}
}
}
})
查詢前5條數(shù)據(jù),如下:

查詢游標為'2019-01-01 05:06:53'后5條數(shù)據(jù),如下:

基于游標的分頁可以解決因數(shù)據(jù)變化查詢出相同數(shù)據(jù)的問題,適用于變化的無限列表加載。如朋友圈的動態(tài),每次加載時基于上次加載的時間游標查詢,可避免出現(xiàn)重復的動態(tài),而不管之前的動態(tài)是否有變化。
Relay風格的分頁
relay是facebook推出的在React中易于使用GraphQL的框架,其中有一套完整的分頁解決方案。
查詢參數(shù):
-
first: 指定取游標后的多少個數(shù)據(jù),與after搭配使用 -
after: 開始游標,與first搭配使用 -
last: 指定取游標前的多少個數(shù)據(jù),與before搭配使用 -
before: 結束游標,與last搭配使用
relay風格的分頁格式定義細節(jié)見:https://facebook.github.io/relay/graphql/connections.htm#
一個relay風格的分頁實現(xiàn)如下:
const users = Mock.mock({
'list|100': [{
'id|+1': 1000,
'name': /user-[a-zA-Z]{4}/
}]
}).list
const User = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: GraphQLInt },
name: { type: GraphQLString }
}
})
// 實際返回的數(shù)據(jù)對象
const UserEdge = new GraphQLObjectType({
name: 'UserEdge',
fields: {
cursor: { type: GraphQLInt }, // 每個對象必須包含游標字段
node: { type: User } // 實際的數(shù)據(jù)對象
}
})
// 分頁信息
const PageInfo = new GraphQLObjectType({
name: 'PageInfo',
fields: {
// 是否有下一頁,該字段必須
hasNextPage: { type: GraphQLBoolean },
// 是否有上一頁,該字段必須
hasPreviousPage: { type: GraphQLBoolean },
// 總頁數(shù),根據(jù)實際情況添加
totalPageCount: { type: GraphQLInt },
// 總數(shù)據(jù)量,根據(jù)實際情況添加
totalCount: { type: GraphQLInt }
}
})
const UserConnection = new GraphQLObjectType({
name: 'UserConnection',
fields: {
edges: { type: new GraphQLList(UserEdge) },
pageInfo: { type: PageInfo }
}
})
const queryObjectType = new GraphQLObjectType({
name: 'RootQuery',
fields: {
users: {
type: UserConnection,
args: {
frist: { type: GraphQLInt },
after: { type: GraphQLInt },
last: { type: GraphQLInt },
before: { type: GraphQLInt }
},
resolve (parent, { frist, after, last, before }) {
// 起始游標和結束游標至少存在一個
if (frist == null && last == null) {
throw new Error('invalid params')
}
let data
let hasNextPage
let hasPreviousPage
const { length: total } = users
if (frist) {
// 根據(jù)起始游標和需要的數(shù)量計算
const index = findIndex(users, one => one.id === after)
data = users.slice(index + 1, index + 1 + frist)
hasNextPage = index + 1 + frist < total
hasPreviousPage = index > 0
} else {
// 根據(jù)結束游標和需要的數(shù)量計算
const index = findIndex(users, one => one.id === before)
data = users.slice(Math.max(index - last, 0), index)
hasNextPage = index + 1 < total
hasPreviousPage = index - last > 0
}
return {
edges: data.map(one => ({ node: one, cursor: one.id })),
pageInfo: {
hasNextPage,
hasPreviousPage,
totalCount: total,
totalPageCount: Math.ceil(total / (frist || last))
}
}
}
}
}
})
根據(jù)起始游標和需要的數(shù)量查詢,如下:

根據(jù)結束游標和需要的數(shù)量查詢,如下:

relay風格的分頁定義的參數(shù)是比較全面的,并且可以根據(jù)需求去擴展pageInfo對象,基本上適用于所有分頁場景,如列表、表格等,只是需要考慮實際場景中sql的優(yōu)化。
自定義分頁格式
以上介紹的幾種分頁方式對于表格的分頁不是很常用,大部分web端的表格分頁是使用page和pageSize來分頁的。以下是自定義分頁方式的實現(xiàn):
const users = Mock.mock({
'list|100': [{
'id|+1': 1000,
'name': /user-[a-zA-Z]{4}/
}]
}).list
const User = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: GraphQLInt },
name: { type: GraphQLString }
}
})
const UserPagination = new GraphQLObjectType({
name: 'UserPagination',
fields: {
data: { type: new GraphQLList(User) },
totalCount: { type: GraphQLInt }, // 總數(shù)量
totalPageCount: { type: GraphQLInt } // 總頁數(shù)
}
})
const queryObjectType = new GraphQLObjectType({
name: 'RootQuery',
fields: {
users: {
type: UserPagination,
args: {
page: { type: GraphQLInt }, // 當前處于第幾頁
pageSize: { type: GraphQLInt } // 分頁大小
},
resolve (parent, { page, pageSize }) {
const data = users.slice((page - 1) * pageSize, page * pageSize)
const { length: total } = users
return {
data,
totalCount: total,
totalPageCount: Math.ceil(total / pageSize)
}
}
}
}
})
查詢第3頁,分頁大小為5的數(shù)據(jù),如下:

自定義分頁查詢展示了常見表格分頁處理的方式,這里想說明的是在GraphQL中完全可以按照適合自己前端處理的方式來定義分頁格式,而不局限于常見的分頁方式。
總結
本文展示了GraphQL中常見的分頁方式,在實際的使用中應根據(jù)客戶端的需求來選擇哪種方式,如果使用了Graph的客戶端框架(如relay),通常分頁的方式就固定下來了,需要服務端對應去實現(xiàn)客戶端所要求的分頁參數(shù)和返回形式。
本文參考資源如下: