使用中继js向前和向后分页
Forward and backward pagination with relay js
中继光标连接规范says:
hasNextPage
will be false if the client is not paginating with first
,
or if the client is paginating with first
, and the server has
determined that the client has reached the end of the set of edges
defined by their cursors.
我从该规范中了解到,Relay 只能在一个页面中分页
方向;向前或向后。对于 infinite 这似乎是合理的
scroll 分页实现。
但是如何实现类似于 Whosebug 问题页面的顺序页面导航
可以双向导航吗?
Relay.js适合这种分页吗?我不能依赖 hasNextPage
和 hasPreviousPage
字段,它们从来没有真正的价值,除非我用 first
和 last
分页,这也非常令人沮丧。
What I understand from that spec is that relay can only paginate with single direction, forwards or backwards.
正确。最初的设计正是这样,一个无限滚动的分页。它从未打算用于双向分页。
But how about sequential pages like Whosebug questions page?
对于目前 Relay 的默认实现来说,这非常棘手,因为如上所述,它不是为双向导航设计的。
也就是说,您可以实现这一目标,尽管不是通过最佳方式。
方法 1 - 通过中继传递页面信息
这可能是最容易实施的方法,尽管不是最佳方法。它涉及将页面信息作为 GraphQL 参数传递给 Relay。每次导航到新页面时都需要一个新的中继查询。
例子
假设您有一个显示成员列表的视图。您使用 memberList
获取 member
个节点的列表。如果您只想在每个“页面”显示 10 个成员,而我们在第 3 页,您可以这样做:
app: () => Relay.QL`
fragment on App {
memberList(page: 3, limit: 10) {
...
}
}`
}
在您的后端,您的 SQL 查询现在看起来像:
SELECT id, username, email FROM members LIMIT 20, 10;
其中20
是起始记录:(page-1)*10
和10
是限制(开始后多少条)。所以上面将获取记录 21-30.
请注意,您需要 page
和 limit
的初始值。所以你的最终查询是:
prepareVariables() {
return {
page: 1,
limit: 10,
};
},
fragments: {
app: () => Relay.QL`
fragment on App {
memberList(page: $page, limit: $limit) {
...
}
}`
}
}
当导航到新页面时,我们需要更新值:
_goToPage = (pg) => {
this.props.relay.setVariables({
page: pg,
});
}
方法 2 - 手动管理游标
这是一个有点老套的实现,因为它本质上为您提供了双向导航,尽管它本身不受支持。这种方法比第一种稍微好一些,但是牺牲了直接跳转到页面的能力。
请注意,我们不能同时使用 first
和 last
:
Including a value for both first and last is strongly discouraged, as it is likely to lead to confusing queries and results.
因此我们将正常构造我们的查询。类似于:
app: () => Relay.QL`
fragment on App {
memberList(first: 10, after: $cursor) {
...
}
}`
}
其中 $cursor
的初始值为 null
。
当您导航到下一页并将 $cursor
的值设置为最后一个成员节点的光标时,您还将该值保存在本地堆栈变量中。
这样,每当您向后导航时,您只需从堆栈中弹出最后一个光标并将其用于您的 after
参数。它在您的应用程序中需要一些额外的逻辑,但这绝对可行。
基于 方法 2 @Chris,
这是我在VueJS中的解决方案,ReactJS也大致相同。
loadPage(type) {
const { endCursor } = this.pageInfo
let cursor = null
if(type === 'previous') {
this.cursorStack = this.cursorStack.slice(0, this.cursorStack.length - 1)
cursor = this.cursorStack[this.cursorStack.length - 1]
}
else {
cursor = endCursor
this.cursorStack = [...this.cursorStack, cursor]
}
this.fetchMore({
variables: { cursor: cursor },
updateQuery: (previousResult, { fetchMoreResult }) => {
if (!fetchMoreResult) return previousResult
return fetchMoreResult
}
})
},
在模板中:
<button :disabled="!cursorStack.length" @click="loadPage('previous')" />
<button :disabled="!pageInfo.hasNextPage" @click="loadPage('next')" />
中继光标连接规范says:
hasNextPage
will be false if the client is not paginating withfirst
, or if the client is paginating withfirst
, and the server has determined that the client has reached the end of the set of edges defined by their cursors.
我从该规范中了解到,Relay 只能在一个页面中分页 方向;向前或向后。对于 infinite 这似乎是合理的 scroll 分页实现。
但是如何实现类似于 Whosebug 问题页面的顺序页面导航 可以双向导航吗?
Relay.js适合这种分页吗?我不能依赖 hasNextPage
和 hasPreviousPage
字段,它们从来没有真正的价值,除非我用 first
和 last
分页,这也非常令人沮丧。
What I understand from that spec is that relay can only paginate with single direction, forwards or backwards.
正确。最初的设计正是这样,一个无限滚动的分页。它从未打算用于双向分页。
But how about sequential pages like Whosebug questions page?
对于目前 Relay 的默认实现来说,这非常棘手,因为如上所述,它不是为双向导航设计的。
也就是说,您可以实现这一目标,尽管不是通过最佳方式。
方法 1 - 通过中继传递页面信息
这可能是最容易实施的方法,尽管不是最佳方法。它涉及将页面信息作为 GraphQL 参数传递给 Relay。每次导航到新页面时都需要一个新的中继查询。
例子
假设您有一个显示成员列表的视图。您使用 memberList
获取 member
个节点的列表。如果您只想在每个“页面”显示 10 个成员,而我们在第 3 页,您可以这样做:
app: () => Relay.QL`
fragment on App {
memberList(page: 3, limit: 10) {
...
}
}`
}
在您的后端,您的 SQL 查询现在看起来像:
SELECT id, username, email FROM members LIMIT 20, 10;
其中20
是起始记录:(page-1)*10
和10
是限制(开始后多少条)。所以上面将获取记录 21-30.
请注意,您需要 page
和 limit
的初始值。所以你的最终查询是:
prepareVariables() {
return {
page: 1,
limit: 10,
};
},
fragments: {
app: () => Relay.QL`
fragment on App {
memberList(page: $page, limit: $limit) {
...
}
}`
}
}
当导航到新页面时,我们需要更新值:
_goToPage = (pg) => {
this.props.relay.setVariables({
page: pg,
});
}
方法 2 - 手动管理游标
这是一个有点老套的实现,因为它本质上为您提供了双向导航,尽管它本身不受支持。这种方法比第一种稍微好一些,但是牺牲了直接跳转到页面的能力。
请注意,我们不能同时使用 first
和 last
:
Including a value for both first and last is strongly discouraged, as it is likely to lead to confusing queries and results.
因此我们将正常构造我们的查询。类似于:
app: () => Relay.QL`
fragment on App {
memberList(first: 10, after: $cursor) {
...
}
}`
}
其中 $cursor
的初始值为 null
。
当您导航到下一页并将 $cursor
的值设置为最后一个成员节点的光标时,您还将该值保存在本地堆栈变量中。
这样,每当您向后导航时,您只需从堆栈中弹出最后一个光标并将其用于您的 after
参数。它在您的应用程序中需要一些额外的逻辑,但这绝对可行。
基于 方法 2 @Chris,
这是我在VueJS中的解决方案,ReactJS也大致相同。
loadPage(type) {
const { endCursor } = this.pageInfo
let cursor = null
if(type === 'previous') {
this.cursorStack = this.cursorStack.slice(0, this.cursorStack.length - 1)
cursor = this.cursorStack[this.cursorStack.length - 1]
}
else {
cursor = endCursor
this.cursorStack = [...this.cursorStack, cursor]
}
this.fetchMore({
variables: { cursor: cursor },
updateQuery: (previousResult, { fetchMoreResult }) => {
if (!fetchMoreResult) return previousResult
return fetchMoreResult
}
})
},
在模板中:
<button :disabled="!cursorStack.length" @click="loadPage('previous')" />
<button :disabled="!pageInfo.hasNextPage" @click="loadPage('next')" />