跳到主要内容

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 参数不会重复订阅;参数语义变化时会取消旧订阅并启动新查询。

  • useGet
  • useFindOne
  • useFindOneOrFail
  • useFind
  • useFindByCursor
  • useFindAll
  • useCount
  • useFindDescendants
  • useCountDescendants
  • useFindAncestors
  • useCountAncestors
  • useGraphNeighbors
  • useCountNeighbors
  • useGraphPaths

查询失败采用 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 是本仓库的硬约束,框架惯例允许容器形态与命名差异,不允许能力缺失:

能力ReactAngularVue
查询useGet / useFind / …同名同名
无限滚动useInfiniteScrolluseInfiniteScrolluseInfiniteScroll
异步操作useAction → 渲染快照useAction → Signal<boolean>useAction → ComputedRef<boolean>
持久化状态usePersistedState → 快照 + setValueusePersistedState / useState → WritableSignal<T>usePersistedState → Ref<T>
实体实时变更useEntityChangeRxDBEntityChangeDirective(markForCheck)useEntityChange

时间窗判定(withTimeWindows)与持久化内核(PersistedStateRegistry)都放在 @aiao/utils 里由三端共用,语义不会各自漂移。Angular 的 usePersistedState 是既有 useState 的扁平签名适配,与 Vue / React 侧键格式一致但各自持有内存状态。

Fileoverview​

RxDB React 集成包 提供 RxDB 数据库的 React Hooks 接口

Interfaces​

InterfaceDescription
ActionResourceuseAction 的返回值。
EntityChangeOptionsuseEntityChange 的时间窗配置。
EntityChangeResource实体 patch 到 React 渲染的桥接结果。
GraphPath图路径结果
InfiniteScrollResource-
NeighborResult图邻居查询结果(基础)
PersistedState某个 namespace + name 的持久化状态。
ProviderPropsRxDBProvider 的 props。
RxDBProviderSet一组相互隔离的 RxDB Provider 与读取 hook。
RxDBResourceReact 查询 hook 在当前 render 返回的资源快照。

Type Aliases​

Type AliasDescription
GraphQueryResult带资源截断状态的图查询数组。
RxDBProviderTypeRxDB Provider 组件类型。
RxDBSourceRxDBProvider 接受的数据库形态:实例、Promise,或返回二者之一的工厂。
SyncStateResourceuseSyncState 在当前 render 返回的同步状态快照。
UseOptionsReact 查询 hook 接受的选项形态。
UseRxDB从显式参数或最近的 Provider 读取 RxDB 的 hook 类型;未就绪时抛错。
UseRxDBOptional从显式参数或最近的 Provider 读取 RxDB 的 hook 类型;未就绪时返回 undefined。

Variables​

VariableDescription
RxDBProvider默认 RxDB Provider;db 可以是实例、Promise 或工厂,见 RxDBSource。
useRxDB读取显式传入的 RxDB,或最近的 RxDBProvider。
useRxDBOptional读取显式传入的 RxDB,或最近的 RxDBProvider,取不到时返回 undefined。

Functions​

FunctionDescription
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读取当前数据库的同步状态。