Daily-It

개발, AI, 인프라, 자동화와 일상 IT 제품 후기를 직접 써보며 정리하는 기술 블로그입니다.

React Native の WatermelonDB とは?オフラインファーストのローカルDBと SQLite の比較

概要

React Native の WatermelonDB これは、SQLite に基づくオフライン ファースト データ レイヤーとして理解するのがより適切です。これは、応答性の高い UI 更新とサーバーとの同期を必要とする、大量のローカルデータを含むアプリを対象としています。これは単純な SQLite ラッパーではありませんが、スキーマ、モデル、クエリ、ライター、オブザーバブル、および同期 API を概要て、アプリがローカルデータ フローを整理できるようにします。少数の設定や短期間のキャッシュのみを保存する場合は重すぎる可能性がありますが、アプリが数千から数万のレコードを処理し、オフラインでもデータの作成、変更、同期を継続する場合は、慎重に評価する価値があります。

まずは SQLite の基本を理解したい場合は、ファイル データベース、WAL、ロック エラー、および該当するシナリオが記載されています。 SQLite 実践ガイド ↗ 別途整理しております。

バージョンの確認: 執筆時点での韓国語原文、npm @nozbe/watermelondb 0.28.0 と表示されていますが、一部の公式ドキュメント ページではまだ 0.27.1 と表示されています。正式導入前に、プロジェクトの React Native/Expo バージョン、WatermelonDB のリリース記録、GitHub のイシューを同時に確認する必要があります。

この記事の内容

なぜこの選定が重要なのか

多くの React Native アプリはサーバー API を呼び出して、結果をページにレンダリングするだけです。この方法は非常に単純ですが、ニーズが変化すると問題が発生します。ネットワークが切断されてもデータを作成または編集する必要がある、起動後に数千のタスク、メッセージ、注文、または顧客データを迅速に表示する必要がある、ローカルデータが変更されたときに複数のページを自動的に更新する必要がある、またはローカルの変更をサーバーと同期する必要があるなどです。 SQLite は安定しており、優れたパフォーマンスを備えていますが、SQLite を直接使用すると、SQL、結果のマッピング、ステータスの更新、移行、同期の競合を自分で設計する必要があります。 WatermelonDB が解決しようとしているのは、このアプリ層の問題です。

WatermelonDB とは

WatermelonDB は、Nozbe のオープンソースのネイティブ データベース フレームワークです。その目標は、アプリの起動時にすべてのデータを一度に JavaScript メモリにロードするのではなく、React および React Native アプリで大量のローカルデータを効率的に処理することです。

  • 起動時にすべての JS を読み取るのではなく、オンデマンドでデータを遅延読み込みします。
  • クエリは SQLite などのネイティブ データベースで実行されます。
  • RxJS ベースのオブザーバブルは、データが変更されたときに UI を更新できます。
  • モデル、コレクション、クエリ、ライター、移行、同期などの API を提供します。
  • オフライン優先度同期を実行する場合は、バックエンドでそれを独自に実装する必要があります。
コンポーネント 効果
Schema テーブルとフィールドを定義します。
Model アプリ コードで使用されるデータ オブジェクトを定義します。
Collection テーブル内のレコードのエントリにアクセスします。
Query API 条件によってフィルタリングされたクエリを実行します。
Writer 作成、変更、削除を安全な書き込みブロックに入れます。
Observable データが変更されたときに UI が自動的に応答できるようにします。
Sync API ローカルとサーバー間で変更を交換する方法を定義します。

大量のローカルデータでも速度を維持できる理由

重要なのは、SQLite が速いということだけではありません。さらに重要なのは、WatermelonDB は、ネイティブ層から JavaScript 層に不要なデータをプルしないようにしようとします。現在のページに必要なデータのみをクエリし、データベース内で完了する条件付きフィルタリングを残し、変更されたデータに関連する監視可能なコンポーネントのみを更新します。

React Native での基本的な使い方

1. インストール

npm install @nozbe/watermelondb
npm install -D @babel/plugin-proposal-decorators
yarn add @nozbe/watermelondb
yarn add --dev @babel/plugin-proposal-decorators

WatermelonDB のサンプルではデコレータ構文を使用しているため、Metro/Babel ではレガシー デコレータを有効にする必要があります。

{
  "presets": ["module:metro-react-native-babel-preset"],
  "plugins": [["@babel/plugin-proposal-decorators", { "legacy": true }]]
}

iOS 側でも CocoaPods のセットアップが必要な場合があります。

# ios/Podfile
pod 'simdjson', path: '../node_modules/@nozbe/simdjson', modular_headers: true
cd ios
pod install

使用 use_frameworks!, Expo または React Native New Architecture プロジェクトでは、最初に小さなブランチで iOS/Android ビルドを検証するのが最善です。

2. スキーマとモデルを定義する

// src/db/schema.js
import { appSchema, tableSchema } from '@nozbe/watermelondb'

export const mySchema = appSchema({
  version: 1,
  tables: [
    tableSchema({ name: 'posts', columns: [
      { name: 'title', type: 'string' },
      { name: 'body', type: 'string' },
      { name: 'is_pinned', type: 'boolean' },
    ]}),
    tableSchema({ name: 'comments', columns: [
      { name: 'body', type: 'string' },
      { name: 'post_id', type: 'string', isIndexed: true },
    ]}),
  ],
})

写真 post_id このフィールドはリレーショナル クエリでよく使用されます。追加することをお勧めします isIndexed: true。作成、変更、削除は次の場所に配置するのが最適です。 database.write() または @writer 処理中です。

import { Q } from '@nozbe/watermelondb'

const pinnedPosts = await database
  .get('posts')
  .query(Q.where('is_pinned', true))
  .fetch()

3. React UIに接続する

import { withObservables } from '@nozbe/watermelondb/react'

function PostItem({ post }) {
  return <Text>{post.title}</Text>
}

export default withObservables(['post'], ({ post }) => ({ post }))(PostItem)

レコードまたはクエリを監視でき、基になるデータの変更に応じて関連する UI が更新されます。

SQLite と WatermelonDB の比較

本当の問題は、どちらが絶対的に優れているかということではなく、どの程度の直接制御が必要か、またどの程度のアプリ層構造をフレームワークに処理してもらいたいかということです。

寸法 SQLiteを直接使用する WatermelonDB の使用
基本的な位置付け ネイティブ リレーショナル データベースエンジン/API SQLite ベースのレスポンシブ データフレームワーク
データアクセス 独自の SQL を記述して結果をマッピングする モデル、コレクション、クエリ API の使用
パフォーマンス 適切に設計されている場合、クエリは非常に高速です 遅延読み込み、ネイティブ クエリ、監視可能な更新
React UI ステータスを管理してリフレッシュしましょう observable は関連する UI を更新できます
同期 すべて自分で実装する 同期プリミティブを提供しますが、バックエンド自体がそれを実装する必要があります。

SQLite を直接使う方が向いているケース

  • ローカルデータの量はそれほど多くありません。
  • 設定、キャッシュ、または最近の検索のみを保存します。
  • SQL の制御とチューニングは、モデルの概要化よりも重要です。
  • React UI を自動的に更新するためにローカルデータを変更する必要はありません。

React Nativeも検討可能 expo-sqlitereact-native-sqlite-storageop-sqlitereact-native-quick-sqlite などの企画。特に万博プロジェクトでは、expo-sqlite おそらくもっと単純です。

WatermelonDB を検討したいケース

  • このアプリは確かにオフラインファーストです。
  • ローカルデータは数千または数万のアイテムに増加する可能性があります。
  • オフラインでもデータの作成や編集が可能。
  • 複数のページが同じローカルデータに応答する必要があります。
  • バックエンド同期プロトコルを実装および維持できます。
  • チームは React Native ネイティブモジュール構築の問題を処理できます。

同期構造

WatermelonDB は同期プリミティブを提供しますが、サーバーを生成しません。クライアントコール synchronize()、提供します pullChanges そして pushChanges;バックエンドは、一貫した変更オブジェクトとタイムスタンプを返す必要があります。

import { synchronize } from '@nozbe/watermelondb/sync'

export async function syncDatabase(database) {
  await synchronize({
    database,
    pullChanges: async ({ lastPulledAt, schemaVersion }) => {
      const response = await fetch(`https://api.example.com/sync?last_pulled_at=${lastPulledAt ?? ''}&schema_version=${schemaVersion}`)
      if (!response.ok) throw new Error(await response.text())
      const { changes, timestamp } = await response.json()
      return { changes, timestamp }
    },
    pushChanges: async ({ changes, lastPulledAt }) => {
      const response = await fetch(`https://api.example.com/sync?last_pulled_at=${lastPulledAt ?? ''}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(changes) })
      if (!response.ok) throw new Error(await response.text())
    },
    migrationsEnabledAtVersion: 1,
  })
}

本当に難しいのはバックエンドです。lastPulledAt 以降の変更を見逃すことはできません。削除レコードは ID によって発行する必要があり、サーバーのタイムスタンプは一貫していて、競合ポリシーは明確でなければなりません。

実際のつまずきやすい場所

ケース 1: デコレータ構文が認識されない

SyntaxError: Support for the experimental syntax 'decorators' isn't currently enabled

まず、依存関係と Metro/Babel 構成が存在することを確認します。よくある間違いは、TypeScript の設定だけを見て、Babel パイプラインがデコレーター構文も処理できる必要があることを忘れることです。

ケース 2: iOS の場合 simdjson.h またはポッド付近でビルドが失敗する

まず ios/Podfile を確認し、再実行 pod install、公式のインストールドキュメントを参照してください。プロジェクトで使う場合 use_frameworks!、もっと注意してください。

ケース 3: Expo Go がネイティブモジュールを見つけられない

NativeModules.WMDatabaseBridge is not defined

Expo Go にはネイティブモジュールは含まれません。 WatermelonDB などのライブラリには通常、Expo 開発ビルドと構成/ネイティブ設定が必要です。要件が非常に単純な場合は、範囲を次のように減らすこともできます。 expo-sqlite

ケース 4: New Architectureの互換性が不確実である

React Native 0.7x 以降、New Architecture、ブリッジレス、新しい Expo SDK、および WatermelonDB の組み合わせでは、最初に問題を確認する必要があります。

npm view @nozbe/watermelondb version
npm view react-native version

ケース 5: 重複または欠落したデータの同期

同期が中心的な要件である場合は、WatermelonDB のインストールよりもバックエンド プロトコルの設計の方が重要です。サーバーのタイムスタンプがいつ生成されたか、プル中の変更が見逃された可能性があるかどうか、削除されたレコードが ID のリストとして返されるかどうか、サーバーが競合を拒否するか受け入れるかを確認します。

いつ使うべきで、いつ見送るべきか

向いている

  • オフラインの優先度は明らかに必要です。
  • ローカルデータ量が多く、起動速度が重要です。
  • リレーショナル データと応答性の高いページ更新が必要です。
  • 信頼性の高い同期バックエンドを実装できます。
  • チームは React Native ネイティブモジュールの問題をトラブルシューティングできます。

見送った方がよいケース

  • 設定または短期 API キャッシュのみを保存します。
  • Expo Go のみを使用でき、ネイティブ ビルドは管理できません。
  • 新しい RN アーキテクチャに切り替えると、互換性を検証する時間がなくなります。
  • モデル化されたデータ層よりも複雑な SQL 分析が必要です。
  • 同期バックエンドを設計および維持する能力がない。

結論

WatermelonDB は、SQLite を React Native で使いやすくする単なる便利なライブラリではありません。これは、大量のローカルデータ、応答性の高い UI、およびオフライン優先同期のためのデータフレームワークです。ほとんどの要件が存在する場合は、小さなブランチでテストする価値があります。小さなキャッシュまたは少量の設定のみが必要な場合は、より単純な SQLite ライブラリを最初に使う方が現実的です。

参考文献

韓国語原文:この記事は韓国語原文をもとに、日本語読者向けに用語、確認手順、注意点を整理しています。韓国語原文を見る