# Render-as-You-Fetch (结合 Suspense)
Observable-hooks 提供了专门的 React Suspense 支持!支持同步模式!
也可以看看 suspense 样例项目。
# 使用响应式数据源的好处
# 多次推值
因为响应式实现了多次推值协议:
单次 | 多次 | |
---|---|---|
拉值 | Function | Iterator |
推值 | Promise | Observable |
你可以一直 next
推新的值而无须推一遍换一个数据源。
# 竞态条件
你无须为了解决资源争用的竞态条件而将数据源写到状态中:
const initialResource = fetchProfileData(0);
function App() {
const [resource, setResource] = useState(initialResource);
直接 switchMap
即可。
# 高级控制
通过丰富的 Observable 操作符你可以轻易串联多个请求、超时处理、失败重试或者其它针对多个流的复杂操作。
# 使用
与 React 官方文档的 Render-as-You-Fetch (using Suspense) 一样,我们先定义数据源,然后在组件中结合 Suspense Context 使用。
# Observable Resource
ObservableResource
将 Observables 转换为类 Relay 的兼容 Suspense 的数据源。
// api.js
import { ObservableResource } from 'observable-hooks'
const postResource$$ = new Subject()
export const postsResource = new ObservableResource(postResource$$.pipe(
switchMap(id => fakePostsXHR(id))
))
export function fetchPosts(id) {
postResource$$.next(id)
}
# Observable Suspense Hook
你可以通过 resource.read()
读取数据源的值。但因为 Observable 可以多次推值,我们还需要在特定时机重新触发 Suspense。 ObservableResource
实例暴露了一个 shouldUpdate$$
Subject,当需要重新触发 Suspense 时它会产生值。
但你无须处理这些细节,Observable-hooks 还提供一个轻量的 hook useObservableSuspense
来正确地接入 Observable 数据源。
// App.jsx
import { useObservableSuspense } from 'observable-hooks'
import { postsResource, fetchPosts } from './api'
fetchPosts('crimx')
function ProfilePage() {
return (
<Suspense fallback={<h1>Loading posts...</h1>}>
<ProfileTimeline />
</Suspense>
)
}
function ProfileTimeline() {
// Try to read posts, although they might not have loaded yet
const posts = useObservableSuspense(postsResource)
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.text}</li>
))}
</ul>
)
}
# Stale-While-Revalidate 模式
默认情况下 ObservableResource
把每个产生的值当作是“成功”的状态。这种情况下当新的值产生时,组件便直接重新渲染。
这也被叫做 Stale-While-Revalidate 模式, 一个从 HTTP RFC 5861 广泛流传的缓存失效策略。
它首先从缓存中返回数据,然后发请求,最终切换到新的值。
# 多次触发 Suspense
要多次触发 Suspense, ObservableResource
还接受一个额外的回调参数用于判断新产生的值是否为“成功”状态,如果不是则重新触发 Suspense。
export const userResource = new ObservableResource(
userResource$$,
// Trigger Suspense on null and undefined
value => value != null
)
在 TypeScript 中如果产生值最终得到的类型与传入的类型不一样,你可以将回调定义为断言函数:
interface Success {
status: 'success'
value: string
}
interface Pending {
status: 'pending'
}
type State = Success | Pending
const input$$ = new Subject<State>()
const resource = new ObservableResource(
input$$,
(value: State): value is Success => value.status !== 'pending'
)
# 异常处理
Observables 产生的异常会被 ObservableResource
收集并作为渲染异常重新抛出。参照 React 文档定义错误边界组件。
同时注意由于 RxJS 的设计,一个 Observable 一旦产生了异常便会被永远断流。要么避免异常传到 Observables 上,要么在子流中使用 catchError
。
如果 Observable 确实产生了异常,可以在需要恢复组件时(如错误边界组件中处理)重载数据源。 对于 Cold Observable 调用 resource.reload()
,对于 Hot Observable 调用 resource.reload(newObservable$)
。建议尽可能在 ObservableResource
使用 Cold Observable 以便重载。