※記事執筆時点のReact Nativeのバージョンは0.43です
はじめに
iOSやAndroidのアプリでは、サーバーAPIから新しいデータを取得する前に、キャッシュしておいたデータをユーザーに対して表示するということはよくあります。アプリが終了してもキャッシュを保持しておきたい場合、対象のデータを端末に保存する(永続化する)必要があります。
今回は、React Nativeでの開発において、データを永続化するためにどういう手段があるのか調べてみました。
React Nativeとデータ永続化
React Nativeでデータを永続化する方法は色々あります。React Native標準の仕組みにはAsyncStorageというものがあり、iOSとAndroidに両方対応しています。
AsyncStorageの他には、Realm、SQLite、Couchbase、MongoDBといった選択肢があります。
awesome-react-native で今日時点でスターが多いライブラリは、realmや react-native-storage (AsyncStorageのラッパー)や react-native-sqlite-storage (SQLiteのラッパー)となっています。
- react-native-couchbase-lite ★71 - couchbase lite binding for react-native
- react-native-db-models ★145 - Local DB Models for React Native Apps
- react-native-level-fs ★12 - fs for react-native using level-filesystem and asyncstorage-down
- react-native-mongoose ★10 - A AsyncStorage based mongoose like storage for react-native
- react-native-pouchdb ★30 - Run pouchdb in React Native!
- react-native-simple-store ★344 - A minimalistic wrapper around React Native's AsyncStorage.
- react-native-sqlite-storage ★668 - SQLite3 bindings for React Native (Android & iOS)
- react-native-sqlite ★474 - SQLite3 bindings for React Native
- react-native-sqlite-2 ★13 - SQLite3 Native Plugin for React Native for both Android and iOS
- react-native-storage ★715 - This is a local storage wrapper for both react-native(AsyncStorage) and browser(localStorage). ES6/babel is needed.
- react-native-store ★442 - A simple database base on react-native AsyncStorage.
- realm ★1755 - An alternative mobile database to SQLite & key-value stores.
- pouchdb-adapter-react-native-sqlite ★11 - PouchDB adapter using ReactNative SQLite as its backing store
- react-native-persistent-job ★55 - Run async tasks that retry after a crash, connection loss or exception
AsyncStorage
AsyncStorageは、シンプルな、暗号化されていない、非同期の、永続的な、key-valueストアです。
Androidアプリの場合は、RocksDBかSQLiteのどちらか利用可能な方にデータが保存されます。
iOSアプリの場合は、ネイティブのコードが実行され、シリアライズされたdictionaryとしてデータがファイルに保存されます。React Nativeのコードをざっと読んだところ、ネイティブの writeToFile
メソッドでファイルを書き込むという実装になっていました。
コード
公式ドキュメントのコードです。各メソッドではPromiseオブジェクトが返却されます。
try {
await AsyncStorage.setItem('@MySuperStore:key', 'I like to save it.');
} catch (error) {
// Error saving data
}
try {
const value = await AsyncStorage.getItem('@MySuperStore:key');
if (value !== null){
// We have data!!
console.log(value);
}
} catch (error) {
// Error retrieving data
}
margeItem()
やmultiMerge()
のメソッドを使うと、既に存在するキー・値と、新しい入力の値を統合することができます。
let UID123_object = {
name: 'Chris',
age: 30,
traits: {hair: 'brown', eyes: 'brown'},
};
// You only need to define what will be added or updated
let UID123_delta = {
age: 31,
traits: {eyes: 'blue', shoe_size: 10}
};
AsyncStorage.setItem('UID123', JSON.stringify(UID123_object), () => {
AsyncStorage.mergeItem('UID123', JSON.stringify(UID123_delta), () => {
AsyncStorage.getItem('UID123', (err, result) => {
console.log(result);
});
});
});
// Console log result:
// => {'name':'Chris','age':31,'traits':
// {'shoe_size':10,'hair':'brown','eyes':'blue'}}
Realm
iOSやAndroidのネイティブ開発で採用される例が増えている、モバイルアプリケーション向けデータベースです。
Realm: リアクティブなモバイルアプリを短期間にの記事では、以下がRealmを使うべき理由であると書かれています。
- 使い方が簡単
- 速い!
- クロスプラットフォーム
- 先進的
- 信頼性
- コミュニティドリブン
- サポート
Realmはデータベースであり、(特にiOSの場合、データをファイルに書き込む)AsyncStorageとは仕組みが異なります。AsyncStorageやそのラッパーを使った場合とは、クエリの使い勝手や速度面で差が出そうです。
なお上記記事のベンチマーク結果では、20万件のレコード(1000件のマッチ)のクエリにおいて、SQLiteよりも速く、AsyncStorageの185倍のパフォーマンスが出ています。
コード
Realm JavaScript 2.0.12 のコードです。
スキーマを定義する必要があります。
ネイティブ開発でRealmを使ったことがある人は、戸惑うことなくコードを書いていけそうです。
const Realm = require('realm');
// Define your models and their properties
const CarSchema = {
name: 'Car',
properties: {
make: 'string',
model: 'string',
miles: {type: 'int', default: 0},
}
};
const PersonSchema = {
name: 'Person',
properties: {
name: 'string',
birthday: 'date',
cars: 'Car[]',
picture: 'data?' // optional property
}
};
Realm.open({schema: [CarSchema, PersonSchema]})
.then(realm => {
// Create Realm objects and write to local storage
realm.write(() => {
const myCar = realm.create('Car', {
make: 'Honda',
model: 'Civic',
miles: 1000,
});
myCar.miles += 20; // Update a property value
});
// Query Realm for all cars with a high mileage
const cars = realm.objects('Car').filtered('miles > 1000');
// Will return a Results object with our 1 car
cars.length // => 1
// Add another car
realm.write(() => {
const myCar = realm.create('Car', {
make: 'Ford',
model: 'Focus',
miles: 2000,
});
});
// Query results are updated in realtime
cars.length // => 2
})
.catch(error => {
console.log(error);
});
所感
シンプルな設定値を扱う場合や、検索条件を指定して取得する必要が無いデータを扱う場合は、AsyncStorageだと機能を手軽に実装できます。iOSネイティブ開発でNSUserDefaultsに保存していたようなデータもAsyncStorageで扱えばよいでしょう。
処理速度が重要になる場合や、iOSネイティブ開発においてCoreDataで管理するようなデータを扱う場合は、予めRealmや他のDBを検討しておくと良さそうです。
さいごに
ベンチマークについても書きたかったのですが、今日はここまで。運用についての知見も得られれば、別途書きたいと思います。
参考
- AsyncStorage · React Native
- Realm: リアクティブなモバイルアプリを短期間に
- Realm React Nativeを公開
- jondot/awesome-react-native: Awesome React Native components, news, tools, and learning material!
- What are my options for storing data when using React Native? (iOS and Android) - Stack Overflow
- React Nativeにおけるローカルデータベースの考察 | Waza Lab
- React NativeでTwitterクライアントを作ってみよう
- React NativeでRealmを使ったアプリ設定の簡易保存 - Qiita