watermelondb

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

WatermelonDB Model & Observation

WatermelonDB 模型与观测

Overview

概述

This skill covers WatermelonDB models (
database/models/
), observation (reactive queries), and ensuring React re-renders when observed data changes. Use it when working with
findAndObserve
,
query.observe()
,
withObservables
, or any screen that subscribes to DB changes.

本技能涵盖WatermelonDB模型(
database/models/
)、观测(响应式查询)以及确保React在观测数据变更时重新渲染的内容。适用于使用
findAndObserve
query.observe()
withObservables
,或任何订阅数据库变更的页面开发场景。

Observation & React Re-rendering

观测与React重新渲染

findAndObserve
and same-reference emission

findAndObserve
与相同引用发射

  • findAndObserve(id)
    (on a collection): Fetches a record by ID, returns an Observable that emits immediately on subscribe and whenever the record is updated or deleted.
  • When a model is updated (e.g. via
    model.update()
    or
    @writer
    methods), the observable emits the same object reference with updated properties — it does not emit a new model instance.
  • findAndObserve(id)
    (针对集合):通过ID获取一条记录,返回一个Observable,在订阅时立即发射数据,并在记录更新或删除时再次发射
  • 当模型更新时(例如通过
    model.update()
    @writer
    方法),该Observable会发射相同的对象引用,但属性已更新——它不会发射新的模型实例。

React
useState
bailout

React
useState
跳过更新机制

  • useState
    uses
    Object.is
    to decide whether to re-render. Passing the same reference (e.g.
    setPhrase(model)
    ) after an update means no re-render.
  • Result: DB updates (text, note, language, etc.) don’t appear until the user navigates away and back, when a new subscription yields a fresh reference.
  • useState
    使用
    Object.is
    来决定是否重新渲染。如果更新后传递相同的引用(例如
    setPhrase(model)
    ),则不会触发重新渲染
  • 结果:数据库的更新(文本、备注、语言等)不会立即显示,直到用户导航离开并返回页面,此时新的订阅会生成一个新的引用。

Fix: store a wrapper so each emit is a new reference

修复方案:存储包装对象,确保每次发射都是新引用

When subscribing to a single model (e.g.
findAndObserve
) and storing it in React state, don’t store the raw model. Store a wrapper so every emission updates state with a new object:
ts
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;
  • Use
    phrase
    (derived) everywhere in the component. Updates persisted to the DB will re-emit, update
    phraseState
    with a new wrapper, and trigger a re-render.
当订阅单个模型(例如
findAndObserve
)并将其存储到React状态时,不要存储原始模型。应存储一个包装对象,这样每次发射都会用新对象更新状态:
ts
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
    ,从而触发组件重新渲染。

Query
observe()
and arrays

查询
observe()
与数组

  • query.observe()
    emits arrays of models. When the query result set changes, WatermelonDB typically emits a new array reference, so
    setState(results)
    usually triggers re-renders.
  • If you build derived data (e.g.
    linked.filter(...)
    ,
    assignments
    ) in the subscribe callback and
    setState
    that, you’re already passing new references — no extra wrapper needed.

  • query.observe()
    发射模型的数组。当查询结果集变更时,WatermelonDB通常会发射新的数组引用,因此
    setState(results)
    通常会触发重新渲染。
  • 如果您在订阅回调中构建派生数据(例如
    linked.filter(...)
    assignments
    )并将其存入状态,您已经在传递新的引用——无需额外的包装对象。

withObservables
(HOC)

withObservables
(高阶组件)

  • withObservables(triggerProps, getObservables)
    injects observable values as props and always passes a new state object into
    setState
    (e.g.
    { values, isFetching }
    ), so React re-renders on each emission even when model references are unchanged.
  • Use it when you can observe a model (or query) passed as a prop: e.g.
    withObservables(['attempt'], ({ attempt }) => ({ attempt: attempt.observe(), ... }))
    . See
    AttemptCard
    in
    features/lesson/components/AttemptCard.tsx
    .
  • For route params (e.g.
    id
    ) you typically subscribe manually in
    useEffect
    (e.g.
    findAndObserve(id)
    ). In that case, use the wrapper pattern above instead of storing the raw model.

  • withObservables(triggerProps, getObservables)
    将可观测值作为props注入组件,并且始终会将新的状态对象(例如
    { values, isFetching }
    )传递给
    setState
    ,因此即使模型引用未改变,React也会在每次发射时重新渲染。
  • 适用于模型作为props传入的场景:例如
    withObservables(['attempt'], ({ attempt }) => ({ attempt: attempt.observe(), ... }))
    。可参考
    features/lesson/components/AttemptCard.tsx
    中的
    AttemptCard
    组件。
  • 对于路由参数(例如
    id
    ),通常需要在
    useEffect
    手动订阅(例如
    findAndObserve(id)
    )。这种情况下,请使用上述的包装对象模式,而非存储原始模型。

Model patterns in this project

项目中的模型模式

  • Models:
    database/models/
    (e.g.
    Phrase
    ,
    Lesson
    ,
    Attempt
    ,
    Translation
    ,
    Deck
    ). Use
    @field
    ,
    @writer
    , and static helpers (e.g.
    Phrase.findOrCreatePhrase
    ,
    Lesson.addLesson
    ).
  • Observation:
    useDatabase()
    from
    @nozbe/watermelondb/react
    ; then
    collection.findAndObserve(id)
    or
    query.observe().subscribe(...)
    .
  • Schema/tables:
    database/schema.ts
    ; collection access via
    db.collections.get<Model>(TABLE)
    .

  • 模型:位于
    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)
    访问集合。

Quick reference

速查参考

ScenarioPatternRe-render guarantee
Single model by
id
(e.g. detail screen)
findAndObserve
+ wrapper state
{ model, _key }
Yes
Query results (list)
query.observe()
+
setState(results)
or derived structures
Yes (new array/refs)
Model passed as prop
withObservables(['model'], ({ model }) => ({ model: model.observe() }))
Yes (HOC uses new state shape)

场景实现模式重新渲染保障
通过
id
获取单个模型(例如详情页)
findAndObserve
+ 包装状态
{ model, _key }
查询结果(列表页)
query.observe()
+
setState(results)
或派生结构
是(新数组/引用)
模型作为props传入
withObservables(['model'], ({ model }) => ({ model: model.observe() }))
是(高阶组件使用新状态结构)

Resources

参考资源

  • DeepWiki: Nozbe/WatermelonDB
    findAndObserve
    ,
    observe()
    ,
    withObservables
    , model updates.
  • Project:
    PhraseDetailScreen
    ,
    LessonDetailScreen
    ,
    SetDetailScreen
    (findAndObserve + wrapper);
    AttemptCard
    (withObservables).
  • 官方文档Nozbe/WatermelonDB — 包含
    findAndObserve
    observe()
    withObservables
    、模型更新等内容。
  • 项目示例
    PhraseDetailScreen
    LessonDetailScreen
    SetDetailScreen
    (使用findAndObserve + 包装对象);
    AttemptCard
    (使用withObservables)。