PGlite React Hooks 实战指南:用 PGliteProvider 与 useLiveQuery 构建响应式 React 应用
2026/9/14 22:06:42 网站建设 项目流程

PGlite React Hooks 实战指南:用 PGliteProvider 与 useLiveQuery 构建响应式 React 应用

【免费下载链接】pgliteEmbeddable Postgres with real-time, reactive bindings.项目地址: https://gitcode.com/GitHub_Trending/pg/pglite

PGlite 是运行在 WebAssembly 中的可嵌入式 Postgres,而@electric-sql/pglite-react包则把它的 live query 插件封装成了符合 React 习惯的 Hooks API。本文围绕 pglite-react 包的官方文档 与 framework-hooks/react.md,系统讲解PGliteProviderusePGlitemakePGliteProvideruseLiveQueryuseLiveQuery.sqluseLiveIncrementalQuery六个 API 的完整用法、接口签名、底层实现与测试验证,读完即可在 React 项目中实现"数据库变化即组件重渲染"的响应式数据流。

前置条件:安装与 live 扩展

在 React 项目中使用这些 Hooks 之前,需要安装两个包:

npm install @electric-sql/pglite npm install @electric-sql/pglite-react

其中@electric-sql/pglite-reactpackage.json(见 packages/pglite-react/package.json)声明了对react的 peer 依赖为^18.0.0 || ^19.0.0 || ^19.0.0-rc,即支持 React 18 与 React 19;同时依赖@electric-sql/pglite提供底层数据库能力。

还有一个关键前置:所有响应式 Hooks 都构建在 live query 插件之上,因此创建 PGlite 实例时必须启用live扩展,否则db.live命名空间不存在,Hooks 无法工作。创建方式见 live-queries.md:

import { PGlite } from "@electric-sql/pglite" import { live } from "@electric-sql/pglite/live" const db = await PGlite.create({ extensions: { live } })

PGliteProvider:把数据库实例注入组件树

PGliteProvider是一个 Provider 组件,用于把创建好的 PGlite 实例传递给所有子组件,供usePGliteuseLiveQueryuseLiveIncrementalQuery使用。用法是把它包裹在应用根部,并通过db属性传入实例:

import { PGlite } from "@electric-sql/pglite" import { live } from "@electric-sql/pglite/live" import { PGliteProvider } from "@electric-sql/pglite-react" const db = await PGlite.create({ extensions: { live } }) const App = () => { // ... return ( <PGliteProvider db={db}> {/* 子组件 */} </PGliteProvider> ) }

从源码看(packages/pglite-react/src/provider.tsx),PGliteProvider本质上是makePGliteProvider以默认类型PGliteWithLive调用生成的实例:它内部通过createContext<T | undefined>(undefined)创建上下文,PGliteProvider组件则渲染<ctx.Provider value={db}>{children}</ctx.Provider>。也就是说,Provider 本身不创建数据库,只负责"传递";数据库的初始化与生命周期仍由你控制。

usePGlite:在任意子组件中获取数据库实例

usePGlite用于在组件内取回 Provider 提供的 PGlite 实例,之后就可以直接对它执行任意 SQL 操作:

import { usePGlite } from "@electric-sql/pglite-react" const MyComponent = () => { const db = usePGlite() const insertItem = () => { db.query("INSERT INTO my_table (name, number) VALUES ('Arthur', 42);") } return ( <> <button onClick={insertItem}>Insert</button> </> ) }

usePGlite的源码行为值得注意(packages/pglite-react/src/provider.tsx):

  • 它优先返回组件树中上下文里保存的实例;
  • 如果显式传入一个db参数(usePGlite(db)),会直接返回该参数而忽略上下文——这为脱离 Provider 的测试或特殊场景提供了逃生通道;
  • 如果既没有上下文实例也没有显式参数,会抛出错误:'No PGlite instance found, use PGliteProvider to provide one'。这提醒我们,忘记包裹PGliteProvider是这类 Hooks 最常见的运行时错误来源。

对应测试见 packages/pglite-react/test/provider.test.tsx:can receive PGlite用例验证了在PGliteProvider包裹下usePGlite()能取回同一个db引用。

makePGliteProvider:为扩展类型创建强类型 Provider

PGlite 支持扩展机制,扩展会在实例上挂载额外的命名空间与类型(例如 live 的db.live、pgvector 的db.vector)。默认导出的PGliteProvider只具备PGliteWithLive类型,若要同时使用多个扩展并保持类型推断,就需要makePGliteProvider

makePGliteProvider<T>()返回一个带有指定泛型类型TPGliteProvider组件与usePGliteHook 对。典型用法是在项目里创建一个导出模块:

import { PGlite, PGliteInterfaceExtensions } from '@electric-sql/pglite' import { LiveNamespace } from '@electric-sql/pglite/live' import { VectorNamespace } from '@electric-sql/pglite-pgvector' import { makePGliteProvider } from '@electric-sql/pglite-react' const { PGliteProvider, usePGlite } = makePGliteProvider< PGlite & PGliteInterfaceExtensions<{ live: typeof live vector: typeof vector }> >() export { PGliteProvider, usePGlite }

此后项目中导入的usePGlite()返回的db就同时携带livevector命名空间及其完整类型,不再需要手动断言。需要说明的是,LiveNamespaceVectorNamespace等扩展命名空间类型均从对应扩展包导出(如@electric-sql/pglite/live@electric-sql/pglite-pgvector),示例中livevector即对应扩展模块的值,可按实际安装的扩展调整泛型参数。

从 provider.tsx 的实现看,makePGliteProvider每次调用都会创建独立的 Context,因此多个 Provider 体系可以并存而互不干扰。测试can receive PGlite with typed provider(provider.test.tsx)验证了makePGliteProvider<PGliteWithLive>()产出的类型化 Provider/Hook 同样能正确回传实例。

useLiveQuery:让组件随查询结果自动重渲染

useLiveQuery是面向 React 的响应式查询 Hook,它包装了 live 扩展的.live.query()API:当查询所依赖的表发生变化时,组件会自动重新渲染并拿到最新结果。其接口签名如下:

function useLiveQuery<T = { [key: string]: unknown }>( query: string, params: unknown[] | undefined | null, ): Results<T>

两个参数分别是:

  1. SQL 查询字符串;
  2. 查询的可选参数(对应 SQL 中的$1$2占位符),不需要时传null或省略。
import { useLiveQuery } from '@electric-sql/pglite-react' const MyComponent = () => { const maxNumber = 100 const items = useLiveQuery(` SELECT * FROM my_table WHERE number <= $1 ORDER BY number; `, [maxNumber]) return ( <> { items.map((item) => <MyItem item={item} /> ) } </> ) }

useLiveQuery 的重载能力:直接接收 LiveQuery 对象或 Promise

文档描述的接口仅覆盖字符串形式,但源码(packages/pglite-react/src/hooks.ts)揭示了更丰富的重载:useLiveQuery除了字符串查询,还可以接收:

  • 一个已经创建好的LiveQuery<T>对象(来自db.live.query(...)的返回值);
  • 一个Promise<LiveQuery<T>>(即未 await 的db.live.query(...)调用本身)。

对应测试位于 packages/pglite-react/test/hooks.test.tsx 的can take a live query return value directlycan take a live query returned promise directly用例:两者都能在后续对表执行 INSERT 后,自动把最新行反映到 Hook 返回值中。这三种形态统一由内部实现useLiveQueryImpl处理,详见下文"底层实现"。

useLiveQuery.sql:标签模板语法构造查询

useLiveQuery.sqluseLiveQuery的标签模板函数(tagged template)形态,与 PGlite 核心的 模板查询 API 一一对应。它允许把参数直接内插进模板字符串,由底层负责解析成 SQL 与参数数组,无需手动维护$1占位符:

import { useLiveQuery } from '@electric-sql/pglite-react' const MyComponent = () => { const maxNumber = 100 const items = useLiveQuery.sql` SELECT * FROM my_table WHERE number <= ${maxNumber} ORDER BY number; ` // ... }

源码(hooks.ts)显示其实现借助了@electric-sql/pglite/templatequery as buildQuery函数,把模板字符串和插值展开为{ query, params },再交给useLiveQueryImpl。测试updates when query parameter changes(hooks.test.tsx)验证了:当模板内插值从'test1'变为'test2'并触发重渲染后,查询结果会随之更新为对应行的数据。

useLiveIncrementalQuery:把 diff 计算下沉到 Postgres

useLiveIncrementalQuery同样提供响应式重渲染,但它包装的是.live.incrementalQuery()API。二者的核心差异在于 diff 的承担位置:

  • useLiveQuerylive.query)在表变化时于 WASM 内部重跑整个查询;
  • useLiveIncrementalQuerylive.incrementalQuery)在 Postgres 内部维护一张上一状态的临时表,表变化后重跑查询并与上次状态做 diff,只把变化的部分从 WASM 拷贝到 JS。这在结果集大、行宽(wide rows)的场景下性能更好,尤其适合喂给 React 做渲染。

其接口签名:

function useLiveIncrementalQuery<T = { [key: string]: unknown }>( query: string, params: unknown[] | undefined | null, key: string, ): Results<T>

三个参数分别是:

  1. SQL 查询字符串;
  2. 查询的可选参数;
  3. diff 算法所依据的键列名(key column),通常是主键。
import { useLiveIncrementalQuery } from '@electric-sql/pglite-react' const MyComponent = () => { const maxNumber = 100 const items = useLiveIncrementalQuery(` SELECT * FROM my_table WHERE number <= $1 ORDER BY number; `, [maxNumber], 'id') return ( <> { items.map((item) => <MyItem item={item} /> ) } </> ) }

关于key参数对 diff 的意义,可参考 live/interface.ts 中LiveIncrementalQueryOptions的定义:{ query, params, key, callback, signal }。底层live.changes依赖该键比对行差异,并在ChangeUpdate/ChangeInsert中通过__after__字段记录"该行应排在哪个 key 之后",从而支持有序结果集内的高效移动(详见 live-queries.md 中对Change类型的说明)。

返回值结构:Results 与 LiveQueryResults

两个 live Hooks 的返回值都是查询结果对象。源码(hooks.ts)对原始LiveQueryResults<T>做了裁剪,返回结构为:

{ rows: T[] // 当前结果行 fields: { name: string; dataTypeID: number }[] // 字段元信息 totalCount?: number // 总行数(窗口化查询时存在) offset?: number // 当前偏移(窗口化查询时存在) limit?: number // 当前限制(窗口化查询时存在) }

注意它不包含affectedRows字段。测试can receive initial results(hooks.test.tsx)展示了返回值的精确形态,例如fields会携带每个列的namedataTypeID(如id列对应类型 OID 23,name文本列对应 25)。

此外需要留意一个异步细节:初始渲染时返回值可能是undefined,直到 live 查询建立并返回初始结果后才有值。测试中普遍使用waitFor(() => expect(result.current).not.toBe(undefined))等待结果就绪,实际组件里也应做好空值防御(例如items?.rows或可选链)。

底层实现:useLiveQueryImpl 如何做到响应式

把文档抽象成机制,核心都收敛在 packages/pglite-react/src/hooks.ts 的useLiveQueryImpl中,它同时服务于三个公开 Hook(useLiveQuery字符串形式、useLiveQuery.sqluseLiveIncrementalQuery)。其关键机制可以归纳为四点:

1. 订阅与清理(useEffect)

Hook 在useEffect中根据输入形态建立订阅:

  • 字符串查询 +key未定义 →db.live.query<T>(query, currentParams, cb)
  • 字符串查询 +key已定义 →db.live.incrementalQuery<T>(query, currentParams, key, cb)
  • Promise<LiveQuery<T>>→ 在.then中保存 liveQuery、用initialResults初始化 state 并subscribe(cb)
  • 直接传入LiveQuery<T>→ 同样用initialResults初始化并订阅。

effect 的清理函数会cancelled = true并调用unsubscribe()/liveQuery.unsubscribe(cb),确保组件卸载或依赖变化时及时断开订阅,避免内存泄漏与对已卸载组件的 setState。

2. 参数变化的浅比较

paramsEqual使用Object.is逐元素比较新旧参数数组,长度不同或任一元素不同都视为变化,从而触发重新订阅。测试updates when query parameters change(hooks.test.tsx)验证了参数值变化与参数个数变化(['test1']['test1','test2'])两条路径都会让结果正确刷新。

3. 查询语句变化的响应

effect 的依赖数组包含query,因此 SQL 字符串变化时(如测试中SELECT * FROM test改为带 WHERE 的版本)会重建订阅;useLiveQuery.sql通过buildQuery生成的新{ query, params }同样走这条路径。

4. 订阅回调驱动重渲染

live 查询的回调cb每次收到新结果即setResults(results),触发 React 重渲染;返回时再按上文结构裁剪字段。整个链路实现了"Postgres 表数据变化 → WASM 内 live 机制检测 → 回调 → setState → 组件重渲染"的响应式闭环。

从 live 插件看 Hooks 的能力边界

useLiveQuery/useLiveIncrementalQuery的能力上限由 live 扩展决定。参考 live/interface.ts 与 live-queries.md 可知底层LiveNamespace提供三类 API:

  • live.query():适合小结果集、窄行的基础实时查询,PGlite 内机制开销更小;
  • live.incrementalQuery():从live.changes发出的增量变化物化完整结果集,适合大结果集与宽行,也是 React Hooks 的推荐选择;
  • live.changes():更低层的变更流 API,直接输出INSERT/UPDATE/DELETE变更(带__op____changed_columns____after__等字段),可用于实现高效的就地 DOM 更新。

在 React Hooks 语境下,如果组件需要实现窗口化分页,可以直接通过db.live.query({ query, offset, limit, callback })创建带totalCount/offset/limit的窗口查询,再把返回的LiveQuery对象(或 Promise)交给useLiveQuery消费——这正是前面提到的重载形态的典型应用场景。

总结:一套 API 打通 React 与嵌入式 Postgres

@electric-sql/pglite-react提供了从"实例注入"到"响应式查询"的完整闭环:

API作用关键参数
PGliteProvider向组件树注入 PGlite 实例db
usePGlite取回上下文中的实例(可选db覆盖上下文)
makePGliteProvider<T>()生成带扩展类型的 Provider/Hook 对泛型T
useLiveQuery响应式查询(包装live.querySQL、可选参数
useLiveQuery.sql模板字符串形态的响应式查询内插参数
useLiveIncrementalQuery增量 diff 的响应式查询(包装live.incrementalQuerySQL、参数、key 列名

实战落地只需四步:启用live扩展创建 PGlite → 用PGliteProvider包裹应用 → 在子组件用usePGlite或扩展类型化的makePGliteProvider获取实例 → 用useLiveQuery/useLiveIncrementalQuery声明式订阅查询。搭配 React 框架 Hooks 文档、live 查询扩展文档 与 测试用例,即可在浏览器中构建数据与界面实时同步的 React 应用。

【免费下载链接】pgliteEmbeddable Postgres with real-time, reactive bindings.项目地址: https://gitcode.com/GitHub_Trending/pg/pglite

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询