rxdb-react
@aiao/rxdb-react 把 @aiao/rxdb 的响应式仓库查询接入 React 19。
安装
pnpm add @aiao/rxdb @aiao/rxdb-react react react-dom rxjs
Provider
RxDBProvider 的 db 收 RxDBSource:实例、Promise,或返回二者之一的工厂。三个框架包收的是同一个联合类型。db 仍是必填 —— 少传时报错要指得出「你没给数据库」,而不是把正在用 Provider 的人指回 Provider。
import type { RxDB } from '@aiao/rxdb';
import { RxDBProvider } from '@aiao/rxdb-react';
interface AppProps {
db: RxDB;
}
export function App({ db }: AppProps) {
return (
<RxDBProvider db={db}>
<Routes />
</RxDBProvider>
);
}
异步形态用于「后端按运行环境动态选择」这类场景 —— 静态 import 会把桌面分支打进 web bundle:
// 注意:非实例的 source 必须是稳定引用(模块级常量或 useMemo 包住)。
// 每次 render 新建一个工厂,会让 Provider 反复重建数据库。
const source = useMemo(() => async () => (await import('./setup-desktop')).setupDesktop(), []);
<RxDBProvider db={source}>…</RxDBProvider>;
读取分两条,区别只有「没就绪该怎么办」:
const database = useRxDB(); // 未就绪抛错,创建失败原样抛出创建异常
const maybe = useRxDBOptional(); // 无 Provider 或未就绪时返回 undefined,用于渲染 loading 态
生命周期所有权:Provider 只销毁自己造的东西。 传工厂或 Promise,实例由 Provider 等来,卸载时它负责 disconnectAll();传已就绪的实例,它归调用方所有,Provider 不碰 —— 否则 StrictMode 的双挂载(挂载 → 卸载 → 挂载)会断掉调用方的模块级单例,留下一个没人会去重连的死库。这条规则三端逐字相同。
需要隔离多个数据库 context 时使用 makeRxDBProvider<T>() 创建独立的 Provider/hook 对。
查询 Hooks
所有查询 hook 返回 { value, error, isLoading, isEmpty, hasValue }。查询参数既可以直接传值,也可以传返回参数的函数。参数函数可能在一次 render 中被调用多次,每次必须返回结构相等的值;非幂等函数会抛出 TypeError,避免形成无限重渲染。结构相等的 inline 参数不会重复订阅;参数语义变化时会取消旧订阅并启动新查询。
useGetuseFindOneuseFindOneOrFailuseFinduseFindByCursoruseFindAlluseCountuseFindDescendantsuseCountDescendantsuseFindAncestorsuseCountAncestorsuseGraphNeighborsuseCountNeighborsuseGraphPaths
查询失败采用 stale-while-error 语义:value 保留最后一次成功值,error 保存原始 Error,hasValue 变为 false,isEmpty 变为 undefined。
无限滚动
useInfiniteScroll 通过仓库的 findByCursor 加载页面,返回 { value, error, isLoading, isEmpty, hasMore, loadMore, refresh }。
const todos = useInfiniteScroll(Todo, {
where: { combinator: 'and', rules: [] },
orderBy: [
{ field: 'createdAt', sort: 'desc' },
`{ field: 'id', sort: 'asc' }`
],
limit: 50
});
loadMore() 在加载中或没有下一页时不会重复请求。refresh() 会取消现有页面订阅并从第一页重新加载。
异步操作
useAction 把一个异步函数包成带在途状态的 action:
const save = useAction((todo: Todo) => repository.save(todo));
return (
<button disabled={save.isPending} onClick={() => save.execute(todo)}>
`{save.isPending ? '保存中…' : '保存'}`
</button>
);
isPending 是并发计数而不是布尔开关:N 次调用同时在途时它一直为真,直到最后一个 settle;计数在 finally 里回退,因此失败也会正确复位。有意不做去重与取消 —— 重复点击会真的执行多次,错误原样 reject 给调用方。
execute 的函数 identity 跨渲染稳定,可以直接进 useEffect / useCallback 的依赖数组,同时调用的始终是最新一次渲染传入的函数。
持久化状态
usePersistedState 把一份状态持久化到 localStorage,同 namespace + name 共享同一份状态:
const theme = usePersistedState('my-app', 'theme', 'dark');
return <button onClick={() => theme.setValue('light')}>{theme.value}</button>;
- 后续调用传入的
initialValue会被忽略,但仍参与类型标签校验 —— 同 key 换值类型直接抛错,而不是静默串型。 namespace与name在键里各自转义,不会互相串号;含:或%的旧键在首次读取时一次性迁移。- 是快照语义:对象原地改字段不会重渲染也不会落盘,必须整体
setValue。setValue的 identity 跨渲染稳定。 - 订阅走
useSyncExternalStore,并发渲染与StrictMode下都不会读到撕裂的快照。 - 写盘失败不抛错,内存值照常更新,失败经
persistError暴露 —— 这是唯一能知道数据没落盘的途径。 - SSR 下不读也不写
localStorage,退化成纯内存值;暂不监听storage事件,因此不跨标签页同步。
实体实时变更
实体是原地可变的类实例,引用不变,React 的 props/state 比较看不到它们的字段变化。useEntityChange 把实体的 patches$ 接进渲染依赖:
// 输入停止 200ms 后才重渲染
const live = useEntityChange(todo, { debounceTime: 200 });
return <div>{live.value?.title}</div>;
debounceTime 与 auditTime 单位是毫秒,同时设置时串联生效(顺序 debounceTime → auditTime),仅正有限值生效:0、负值、NaN、Infinity 一律表示禁用,两者都禁用时 patch 同步透传。revision 记录已收到的 patch 次数,跨实体切换继续累加;error 在实体或时间窗切换时复位。
三端 API 对照
同功能同 API 是本仓库的硬约束,框架惯例允许容器形态与命名差异,不允许能力缺失:
| 能力 | React | Angular | Vue |
|---|---|---|---|
| 查询 | useGet / useFind / … | 同名 | 同名 |
| 无限滚动 | useInfiniteScroll | useInfiniteScroll | useInfiniteScroll |
| 异步操作 | useAction → 渲染快照 | useAction → Signal<boolean> | useAction → ComputedRef<boolean> |
| 持久化状态 | usePersistedState → 快照 + setValue | usePersistedState / useState → WritableSignal<T> | usePersistedState → Ref<T> |
| 实体实时变更 | useEntityChange | RxDBEntityChangeDirective(markForCheck) | useEntityChange |
时间窗判定(withTimeWindows)与持久化内核(PersistedStateRegistry)都放在 @aiao/utils 里由三端共用,语义不会各自漂移。Angular 的 usePersistedState 是既有 useState 的扁平签名适配,与 Vue / React 侧键格式一致但各自持有内存状态。
Fileoverview
RxDB React 集成包 提供 RxDB 数据库的 React Hooks 接口
Interfaces
| Interface | Description |
|---|---|
| ActionResource | useAction 的返回值。 |
| EntityChangeOptions | useEntityChange 的时间窗配置。 |
| EntityChangeResource | 实体 patch 到 React 渲染的桥接结果。 |
| GraphPath | 图路径结果 |
| InfiniteScrollResource | - |
| NeighborResult | 图邻居查询结果(基础) |
| PersistedState | 某个 namespace + name 的持久化状态。 |
| ProviderProps | RxDBProvider 的 props。 |
| RxDBProviderSet | 一组相互隔离的 RxDB Provider 与读取 hook。 |
| RxDBResource | React 查询 hook 在当前 render 返回的资源快照。 |
Type Aliases
| Type Alias | Description |
|---|---|
| GraphQueryResult | 带资源截断状态的图查询数组。 |
| RxDBProviderType | RxDB Provider 组件类型。 |
| RxDBSource | RxDBProvider 接受的数据库形态:实例、Promise,或返回二者之一的工厂。 |
| SyncStateResource | useSyncState 在当前 render 返回的同步状态快照。 |
| UseOptions | React 查询 hook 接受的选项形态。 |
| UseRxDB | 从显式参数或最近的 Provider 读取 RxDB 的 hook 类型;未就绪时抛错。 |
| UseRxDBOptional | 从显式参数或最近的 Provider 读取 RxDB 的 hook 类型;未就绪时返回 undefined。 |
Variables
| Variable | Description |
|---|---|
| RxDBProvider | 默认 RxDB Provider;db 可以是实例、Promise 或工厂,见 RxDBSource。 |
| useRxDB | 读取显式传入的 RxDB,或最近的 RxDBProvider。 |
| useRxDBOptional | 读取显式传入的 RxDB,或最近的 RxDBProvider,取不到时返回 undefined。 |
Functions
| Function | Description |
|---|---|
| makeRxDBProvider | 创建隔离的 RxDB React context 与访问 hook。 |
| useAction | 把一个异步函数包装成带在途状态的 action。 |
| useCount | 统计匹配实体数量。 |
| useCountNeighbors | 统计图实体的邻居数量。 |
| useEntityChange | 把实体的 patch 流桥接到 React 渲染,让原地修改能实时反映到视图。 |
| useFind | 查找多个匹配实体。 |
| useFindAll | 查找全部实体。 |
| useFindByCursor | 使用游标分页查找实体。 |
| useFindOne | 查找第一个匹配实体。 |
| useFindOneOrFail | 查找第一个匹配实体,仓库未找到时把错误写入资源。 |
| useGet | 通过 ID 获取单个实体。 |
| useGraphNeighbors | 查找图实体的邻居。 |
| useGraphPaths | 查找图实体之间的路径。 |
| useInfiniteScroll | 使用 RxDB 游标查询提供无限滚动状态。 |
| usePersistedState | 创建(或复用)一份持久化到 localStorage 的命名空间状态。 |
| useRepositoryQuery | 把实体静态仓库的 Observable 查询接入 React render 生命周期。 |
| useSyncState | 读取当前数据库的同步状态。 |