【原創(chuàng)】GraphQL學習:分頁

分頁是十分常見的接口使用場景,該篇文章詳細介紹常見的分頁方式,主要關注于GraphQL中分頁的形式。

相關文章

目錄

  • 基于偏移量分頁
  • 基于游標分頁
  • 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ù),如下:

基于偏移量的分頁查詢結果1

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

基于偏移量的分頁查詢結果2

基于偏移量的分頁通常直接返回數(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ù),如下:

使用id游標的分頁查詢

使用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ù),如下:

使用createAt游標的分頁查詢1

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

使用createAt游標的分頁查詢2

基于游標的分頁可以解決因數(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ù)量查詢

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

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

relay風格的分頁定義的參數(shù)是比較全面的,并且可以根據(jù)需求去擴展pageInfo對象,基本上適用于所有分頁場景,如列表、表格等,只是需要考慮實際場景中sql的優(yōu)化。

自定義分頁格式

以上介紹的幾種分頁方式對于表格的分頁不是很常用,大部分web端的表格分頁是使用pagepageSize來分頁的。以下是自定義分頁方式的實現(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ù),如下:

根據(jù)page和pageSize查詢

自定義分頁查詢展示了常見表格分頁處理的方式,這里想說明的是在GraphQL中完全可以按照適合自己前端處理的方式來定義分頁格式,而不局限于常見的分頁方式。

總結

本文展示了GraphQL中常見的分頁方式,在實際的使用中應根據(jù)客戶端的需求來選擇哪種方式,如果使用了Graph的客戶端框架(如relay),通常分頁的方式就固定下來了,需要服務端對應去實現(xiàn)客戶端所要求的分頁參數(shù)和返回形式。

本文參考資源如下

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容