watermelondb について
WatermelonDBを使ったデータベース連動型React画面の開発者向けに、モデルと監視クエリを解説するスキルです。通知時のオブジェクト参照が再レンダリングに与える影響と、購読や`withObservables`のパターンを示します。
主な機能
- 01`findAndObserve`でレコードを監視
- 02クエリ結果を購読
- 03Reactの参照比較による描画スキップを回避
- 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 を扱う場合や、DB の変更を購読する画面で使用します。
監視と React の再レンダリング
findAndObserve と同じ参照の発行
findAndObserve(id)(コレクション上):ID でレコードを取得し、購読時に即座に、またレコードが更新または削除されるたびに発行する Observable を返します。- モデルが更新されると(例:
model.update()や@writerメソッドによる更新)、Observable は更新後のプロパティを持つ同じオブジェクト参照を発行します。新しいモデルインスタンスは発行しません。
React useState の更新スキップ
useStateは、再レンダリングするかどうかの判断にObject.isを使用します。更新後に同じ参照(例:setPhrase(model))を渡すと、再レンダリングされません。- 結果:DB の更新(テキスト、メモ、言語など)は、ユーザーが画面を離れて戻り、新しい購読によって新しい参照が得られるまで表示されません。
修正方法:ラッパーを保存して、発行ごとに新しい参照にする
単一のモデル(例:findAndObserve)を購読して React の state に保存する場合、モデルそのものは保存しないでください。ラッパーを保存すると、発行のたびに state が新しいオブジェクトで更新されます。
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をすべての箇所で使用します。DB に保存された更新が再発行されると、phraseStateが新しいラッパーで更新され、再レンダリングがトリガーされます。
クエリの observe() と配列
query.observe()はモデルの配列を発行します。クエリ結果セットが変化すると、WatermelonDB は通常、新しい配列参照を発行するため、setState(results)で再レンダリングがトリガーされます。- 購読コールバック内で派生データ(例:
linked.filter(...)、assignments)を作成してsetStateに渡す場合、すでに新しい参照を渡しているため、追加のラッパーは不要です。
withObservables(HOC)
withObservables(triggerProps, getObservables)は Observable の値を props として注入し、setStateに常に新しい state オブジェクト(例:{ values, isFetching })を渡すため、モデルの参照が同じでも発行のたびに React が再レンダリングされます。- props として渡されたモデル(またはクエリ)を監視できる場合に使用します。例:
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(...)を使用します。 - スキーマ/テーブル:
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 対応