关于 watermelondb
面向使用 WatermelonDB 构建 React 数据库界面的开发者,讲解模型与响应式查询。重点说明观测结果的对象引用如何影响重新渲染,并提供订阅和 `withObservables` 模式。
主要功能
- 01用 `findAndObserve` 观测记录
- 02订阅查询结果
- 03避免 React 状态引用导致跳过渲染
- 04用 `withObservables` 注入响应式属性
适用场景
- 01记录更新后刷新详情界面
- 02根据变化的查询结果渲染列表
- 03将观测到的模型属性传入组件
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ jchaselubitz/ drill-app --skill watermelondb安装后在 Claude Code 中输入/watermelondb确认已加载。
附带文件meta.yaml
详细介绍
WatermelonDB 模型与观察
概述
此技能涵盖 WatermelonDB 模型(database/models/)、观察
(响应式查询),以及确保观察到的数据发生变化时 React 会重新渲染。在使用 findAndObserve、query.observe()、
withObservables,或任何订阅数据库变化的屏幕时,请使用此技能。
观察与 React 重新渲染
findAndObserve 与相同引用的发射
findAndObserve(id)(作用于集合):按 ID 获取记录,返回一个 Observable,该 Observable 在订阅时立即发射,且在记录 更新或删除时都会发射。- 更新模型时(例如通过
model.update()或@writer方法),Observable 会以更新后的属性发射同一个对象引用,而不会 发射新的模型实例。
React useState 的提前退出
useState使用Object.is来判断是否重新渲染。在更新后传入相同引用(例如setPhrase(model))意味着不会重新渲染。- 结果:数据库更新(文本、笔记、语言等)不会显示,直到用户离开该页面再返回,此时新的订阅会得到一个新的 引用。
修复:存储包装对象,让每次发射都产生新引用
订阅单个模型(例如 findAndObserve)并将其存储在 React 状态中时,不要直接存储原始模型。存储一个包装对象,这样每次发射都会用新对象更新状态:
const [phraseState, setPhraseState] = useState<
{ phrase: Phrase; _key: number } | null
>(null);
useEffect(() => {
if (!id) return;
const sub = db.collections
.get<Phrase>(PHRASE_TABLE)
.findAndObserve(id)
.subscribe((result) => {
setPhraseState({ phrase: result, _key: result.updatedAt });
});
return () => sub.unsubscribe();
}, [id, db]);
const phrase = phraseState?.phrase ?? null;
- 在组件中使用派生出的
phrase。持久化到数据库的更新会重新发射,用新的包装对象更新phraseState,并触发 重新渲染。
查询 observe() 与数组
query.observe()会发射模型数组。查询结果集发生变化时,WatermelonDB 通常会发射一个新的数组引用,因此setState(results)通常会触发重新渲染。- 如果在订阅回调中构建派生数据(例如
linked.filter(...)、assignments)并对其调用setState,你传入的已经是新引用,无需额外包装。
withObservables(HOC)
withObservables(triggerProps, getObservables)会将 Observable 值作为 props 注入,并且始终向setState传入一个新的状态对象(例如{ values, isFetching }),因此即使模型引用没有变化,React 也会在每次发射时重新渲染。- 当你可以**观察作为 prop 传入的模型(或查询)**时,请使用它:例如
withObservables(['attempt'], ({ attempt }) => ({ attempt: attempt.observe(), ... }))。 请参阅features/lesson/components/AttemptCard.tsx中的AttemptCard。 - 对于路由参数(例如
id),通常会在useEffect中手动订阅(例如findAndObserve(id))。这种情况下,请使用上文的包装对象 模式,而不是存储原始模型。
本项目中的模型模式
- 模型:
database/models/(例如Phrase、Lesson、Attempt、Translation、Deck)。使用@field、@writer和静态辅助方法(例如Phrase.findOrCreatePhrase、Lesson.addLesson)。 - 观察:从
@nozbe/watermelondb/react使用useDatabase();然后调用collection.findAndObserve(id)或query.observe().subscribe(...)。 - Schema/表:
database/schema.ts;通过db.collections.get<Model>(TABLE)访问集合。
快速参考
| 场景 | 模式 | 重新渲染保证 |
|---|---|---|
按 id 查找单个模型(例如详情页) | findAndObserve + 包装器状态 { model, _key } | 是 |
| 查询结果(列表) | query.observe() + setState(results) 或派生结构 | 是(新数组/引用) |
| 将模型作为属性传入 | withObservables(['model'], ({ model }) => ({ model: model.observe() })) | 是(HOC 使用新的状态结构) |
资源
- DeepWiki:Nozbe/WatermelonDB —
findAndObserve、observe()、withObservables、模型更新。 - 项目:
PhraseDetailScreen、LessonDetailScreen、SetDetailScreen(findAndObserve + 包装器);AttemptCard(withObservables)。
评论
支持 Markdown