移動資料庫 Realm 在 React-Native 的使用詳解

蘆葦科技App技術團隊發表於2018-12-13

在開發中有些資料我們需要在本地進行持久化儲存,在需要的地方呼叫。一般來說,我們會用到 AsyncStorage 來對資料進行持久化的儲存,這是官方推薦使用的儲存方式,類似於 iOS 中的 NSUserDefault ,區別在於,AsyncStorage 只能儲存字串鍵值對,而 NSUserDefault 可以儲存字串和 number。如此,當我們需要儲存的資料規模較為龐大時,需要考慮選擇另一種持久化的儲存方式-- Realm

Realm:與 SQList 進行資料儲存相比,在效能上,各有優勢,但是操作上,Realm 有明顯優勢,也更方便使用。其中囊括了各端的使用,包括

接下來我們就來看看怎麼使用 Realm

1.安裝

npm install --save realm

2.連結

react-native link realm

需要注意有兩點:

1.安卓端可能使用 link 無效,這時可以進行以下步驟:

  • android/settings.gradle 內新增:
include ':realm'
project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android')
複製程式碼
  • android/app/build.gradle 增加依賴庫:
// When using Android Gradle plugin 3.0 or higher
dependencies {
  implementation project(':realm')
}

// When using Android Gradle plugin lower than 3.0
dependencies {
  compile project(':realm')
}
複製程式碼
  • MainApplication.java 中匯入並且連結 package:
import io.realm.react.RealmReactPackage; // add this 

import public class MainApplication extends Application implements ReactApplication {
    @Override
    protected List<ReactPackage> getPackages() {
        return Arrays.<ReactPackage>asList(
            new MainReactPackage(),
            new RealmReactPackage() // add this line
        );
    }
}
複製程式碼

2.因為我是在0.56版本上開大,因此需要固定選擇安裝的版本為 2.16.0,記住把2上面的 ^ 去掉。

3.初步使用:

Realm.open({
      schema: [{name: 'Dog', properties: {name: 'string'}}]
    }).then(realm => {
      realm.write(() => {
        realm.create('Dog', {name: 'Rex'});
      });
      this.setState({ realm });
    });
複製程式碼

4.介紹:

Realm JavaScript enables you to efficiently write your app’s model layer in a safe, persisted and fast way. It’s designed to work with React Native

意思是在 RN 上用很快,很安全,很棒棒的。

來看一個例子:

//在檔案中引入 realm
const Realm = require('realm');

// 建立資料模型,並且在 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 使用的特別之處,把建立的資料模型整合到 schema  之中,通過 open 方法開啟一條 Realm
Realm.open({schema: [CarSchema, PersonSchema]})
  .then(realm => {
    // 只能在 write 方法中運算元據,查詢則不用
    realm.write(() => {
    //create 建立資料的方法
      const myCar = realm.create('Car', {
        make: 'Honda',
        model: 'Civic',
        miles: 1000,
      });
      // 更新資料
      myCar.miles += 20; 
    });

    // 查詢,返回陣列
    const cars = realm.objects('Car').filtered('miles > 1000');
    
    //這個時候長度是1
	cars.length // => 1

    // 再次新增另一條資料
    realm.write(() => {
      const myCar = realm.create('Car', {
        make: 'Ford',
        model: 'Focus',
        miles: 2000,
      });
    });

    // 查詢結果更新,變成2
    cars.length // => 2
  })
  .catch(error => {
    console.log(error);
  });
複製程式碼

4.Realm 獨有的管理工具--> Realm Studio

用於檢視儲存在本地的資料

MacLinuxWindows 版本

5.詳解

Realm.open({schema: [Car, Person]})
  .then(realm => {
    // ...use the realm instance here
  })
  .catch(error => {
    // Handle the error here if something went wrong
  });
複製程式碼

open 方法是開啟資料庫的方法, open(config: Realm.Configuration): ProgressPromise;,

還有一個是非同步執行緒上的使用:static openAsync(config: Realm.Configuration, callback: (error: any, realm: Realm) => void, progressCallback?: Realm.Sync.ProgressNotificationCallback): void

Configuration 裡帶有很多引數,我們進去看看:

interface Configuration {
        encryptionKey?: ArrayBuffer | ArrayBufferView | Int8Array;
        migration?: (oldRealm: Realm, newRealm: Realm) => void;
        shouldCompactOnLaunch?: (totalBytes: number, usedBytes: number) => boolean;
        path?: string;
        readOnly?: boolean;
        inMemory?: boolean;
        schema?: (ObjectClass | ObjectSchema)[];
        schemaVersion?: number;
        sync?: Partial<Realm.Sync.SyncConfiguration>;
        deleteRealmIfMigrationNeeded?: boolean;
        disableFormatUpgrade?: boolean;
}
複製程式碼

著重講幾個引數:

  • path:建立一個指定儲存路徑的 Realm,預設是 Realm.realm,也可以自己命名
  • migration:移動功能,把資料遷移到另一個地方
  • sync:一個同步物件,在資料庫服務中開啟 Realm 的同步
  • inMemoryRealm 將在記憶體中開啟,並且物件不會被持久化; 一旦最後一個 Realm 例項關閉,所有物件都會消失
  • deleteRealmIfMigrationNeeded:如果處於開發模式,可以使用這項功能來自動刪除資料
  • schemaVersion
    • 如果表格中新增的欄位,然後不做任何變化的開啟,則程式就會報錯;系統預設開啟的版本是0,為了避免程式報錯,也為了能正確的更新表格資料,應該這樣寫:Realm.open({schema: [Car, Person], schemaVersion: 1});
    • 檢視當前版本:Realm.schemaVersion(Realm.defaultPath);

6.資料模型

當初始化的時候,通過建立的 Realm,資料模型伴隨著 schema 的生成而建立,每個 schema 包含 name(模型名稱),primaryKey(唯一標誌符,通常用於陣列),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
  }
};

// Initialize a Realm with Car and Person models
Realm.open({schema: [CarSchema, PersonSchema]})
  .then(realm => {
    // ... use the realm instance to read and modify data
  })
複製程式碼

7.引數型別

引數型別有7種,bool,int,float,double,string,data,date

注意的點

  • 其中 dataArrayBuffer,具體是什麼我還不太清楚,不過這個並不是陣列的意思。
  • 必填屬性儲存的時候不支援 null 或者 undefined,選填屬性可以儲存這兩種型別的值,因此如果在賦值時有可能屬於這些值時,應該先做好判斷,否則會丟擲異常
  • 選填屬性optional 或者在型別後給個 ? 來做表示,比如:
const PersonSchema = {
  name: 'Person',
  properties: {
    realName:    'string', // 必填屬性
    displayName: 'string?', // 選填屬性
    birthday:    {type: 'date', optional: true}, // 選填屬性
  }
};
複製程式碼
  • 除了儲存單個值之外,還可以將屬性宣告為任何受支援的基本型別的列表。例如儲存類似 JavaScript array 的話,通過將[]附加到型別名稱來完成
const PersonSchema = {
  name: 'Person',
  properties: {
    name: 'string',
    testScores: 'double?[]'
  }
};

let realm = new Realm({schema: [PersonSchema, CarSchema]});

realm.write(() => {
  let charlie = realm.create('Person', {
    name: 'Charlie',
    testScores: [100.0]
  });

  
  charlie.testScores.push(null);

  
  charlie.testScores.push(70.0);
  
  console.log('charlie.testScores==',charlie.testScores)//列印結果:{ '0': 100, '1': null, '2': 70 }
});
複製程式碼
  • 另外,如果需要建立一個屬性中包含多種物件的陣列,那可以往屬性種新增 schema

8.資料操作

  • 更新資料需要在 write 方法內寫,查詢資料則不用,並且最好使用 try/catch 方式以獲取丟擲的異常:
try {
  realm.write(() => {
    realm.create('Car', {make: 'Honda', model: 'Accord', drive: 'awd'});
  });
} catch (e) {
  console.log("Error on creation");
}
複製程式碼
  • 刪除物件:
realm.write(() => {
  // Create a book object
  let book = realm.create('Book', {id: 1, title: 'Recipes', price: 35});

  // Delete the book
  realm.delete(book);

  // Delete multiple books by passing in a `Results`, `List`,
  // or JavaScript `Array`
  let allBooks = realm.objects('Book');
  realm.delete(allBooks); // Deletes all books
});
複製程式碼
  • filtered 查詢資料的時候,引數只能傳常量或者屬性名稱(目前沒找到傳變數的方法,所以這個方法不是很靈活
  • 更新資料除了使用 filtered,還可以使用建立的方法,只要在後面把預設修改狀態改為 true
realm.create('dtList'{realmId :realmId,editDataType:'DELETE'},true);
複製程式碼

需要注意的還有一點:

根據文件上的解釋:

If your model class includes a primary key, you can have Realm intelligently update or add objects based off of their primary key values. This is done by passing true as the third argument to the create method:

需要設定 primary key,這個一開始我以為是隨便填,原來用處是在這裡,這個 primary key 是跟 properties 內的一個引數相關聯,因此需要設定一個類似 id 的主鍵,以此來做修改指定資料的依據。

除了用 id 做主鍵,如果儲存的資料大,並且還涉及到與後臺的資訊同步,那就可以用生成隨機 UDID 的方法:

export function  getUUID(){
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
            var r = Math.random() * 16 | 0,
            v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
 		}).toUpperCase();
}
複製程式碼

這樣在建立資料或者修改的時候就不會出現涉及到被覆蓋的資料。

在結束操作的時候,需要執行關閉資料庫操作的處理,這樣避免 realm 一直佔用執行緒資源,程式發生奔潰現象。

realm.close()

相關文章