-
Notifications
You must be signed in to change notification settings - Fork 56
pagination
teambition-web 应用目前的后端主要使用了三种分页机制:
- url 携带查询参数
page
- 指定页码(从 1 开始),和count
- 指定页长(每一页包含的条目数,最后一页除外);从后端获得对应的条目列表。 - url 携带查询参数
pageToken
- 提示后端上次分页请求的完成状态(即后端接口在上一页请求返回结果中携带的nextPageToken
值;在第一页的请求里,该值设为''
),和pageSize
- 指定当前请求要拿的条目数上限;从后端获得一个对象,包含字段:result
- 条目列表,nextPageToken
- 下一页请求需要携带的pageToken
参数,和totalSize
- 目标结果集当前整体的条目数量(这个字段并不总是存在,因为有些场景后端无法低成本得到全集大小)。 - 与上一种类似,但将分页相关的元信息放到 HTTP headers 里;前端的第一次请求与非分页请求没有差别,不需要携带额外信息;随后,如果要发起额外的分页请求,则在 request headers 里带上上一次后端在 response headers 里携带的
x-custom-pageToken
和x-custom-size
字段。
teambition-sdk 目前提供的 第一版 分页接口只支持上边 第二种,但在设计时考虑了随后对另外两种(特别是第三种)的支持。
需要把分页操作自有的状态在应用层面手动管理。无论是在 redux store 节点多一个 pagination 字段维护这些状态并添加对应的 reducers,还是在 react 组件内部添加 state 和 setState,掺杂在本就复杂的业务逻辑里。
在能方便使用 Observable 的地方,通过一个自定义的操作符来将分页相关状态管理封装起来,外部完全不需要关心,只需要从操作符拿出结果就可以直接使用。
在一些依然是通过一次回调调用,触发一次分页操作的地方,提供状态变量的接管,不需要记得在每次请求之后更新状态变量。
用于表达符合 teambition-sdk 需要的分页状态,类型参数 T
代表结果元素的类型,如成员列表相关的分页接口对应的 T
就很可能是 MemberSchema
。目前设计中管理的字段有:
-
urlPath: string
- 当前分页请求对应的 url 的 path 部分,左右不含/
,可以直接用于 SDKFetch 的接口; -
pageSize: number
- 一页要包含的最大条目数; -
urlQuery: {}
- 当前分页请求对应的 url 的 query 部分,不需要包含 nextPageToken 或 pageSize,如搜索任务数据时,会有{ type: 'task', q: '...' }
; -
nextPageToken: PageToken
- 从上一页的后端返回得到的信息,用于下一页请求; -
totalSize?: number
- 后端在最近的一次返回中告知的结果集整体条目数(可能是 undefined); -
result: T[]
- 实际的结果; -
hasMore: boolean
- 通过后端在最近的一次返回中携带的信息,推算得,是否还存在更多条目可以进一步通过分页获取; -
nextPage: number
- 下一页的页码。
用于生成一个可用的初始化状态 Page.State<T>
。参数列表为:
-
urlPath: string
- 对应生成的Page.State<T>
里的urlPath
字段; -
options
-
pageSize: number
- 对应生成的Page.State<T>
里的pageSize
字段; -
urlQuery: {}
- 对应生成的Page.State<T>
里的urlQuery
字段。
-
生成的 Page.State<T>
的其他字段还有如下初始值:
-
nextPageToken: ''
- 后端定义第一个请求携带的pageToken
是空字符串; -
totalSize: undefined
- 该字段完全由后端判定,在未发起第一次请求之前,未知; -
result: []
- 因为一页都还没载入,结果集为空; -
hasMore: true
- hasMore 为 false 会导致在某些情况下不发请求,所以设为 true,方便发起第一次请求; -
nextPage: 1
- 在未完成第一次请求时,下一页,就是第一页。
用于按需发起请求,帮助用户管理分页状态(每次请求得到的结果集接在当前 result: T[]
的尾部,hasMore: boolean
更新等等),并推出最新的分页状态 Observable<Page.State<T>>
。
在能方便使用 Observable 的场景里,推荐使用 sdkFetch.expandPage(initialState, { loadMore$ })
,如在 epic 里:
const firstPage$ = actions$.ofType('REQUEST_MEMBERS')
const restPages$ = acitons$.ofType('REQUEST_MEMBERS_LOADMORE')
return firstPage$.switchMap(() => {
const initialState = Page.defaultState<MemberSchema>(
'organization/members',
{ pageSize: 50 }
)
return sdkFetch.expandPage(initialState, { loadMore$: restPages$ })
.map(({ result, hasMore }) => actions.requestMembersSuccess({
members: result,
hasMoreMembers: hasMore
}))
.catch((error) => actions.requestMembersFailure({ error: error.message }))
})
在完成第一次请求之后,会在每次 loadMore$
流有推出时,进行下一页的请求。
在使用回调,通过回调的一次次调用出发一次次分页请求的场景里,推荐使用 sdkFetch.expandPage(state, { mutate: true })
,如在 react 组件里:
class C extends React.PureComponent {
private pageState: Page.State<GroupSchema> | undefined
componentDidMount() {
this.pageState = Page.defaultState<GroupSchema>(
'organization/groups',
{ pageSize: 50 }
)
}
private loadGroupsInPages = () => {
sdkFetch.expandPage(this.pageState, { mutate: true })
.take(1)
.subscribe(({ result, hasMore }) => {
this.setState({ groups: result, hasMoreGroups: hasMore })
})
}
...
render() {
const { groups, hasMoreGroups } = this.state
return (
<>
<Groups groups={ groups } />
{ hasMoreGroups ? <DDDot onEnter={ this.loadGroupsInPages } /> : <Dot /> }
</>
)
}
}
这里,通过设置 { mutate: true }
,每次对 sdkFetch.expandPage
的调用在请求完成后都会更新 this.pageState
的内部状态。
这个函数还有不少其他 options,详情参考具体定义。