WhatAKitty Daily

A Programmer's Daily Record

【翻译】Realm Javascript 1.12.0文档

WhatAKitty   阅读次数loading...
  • 2017-10-14 创建
  • 2017-10-15 更新
  • 2017-10-16 更新

介绍

Realm Javascript版本能够帮助你安全、持续、快速有效的构建你的model层。Realm Javascript被设计用于React Native和Nodejs应用。

这里有个快速简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
const Realm = require('realm');

// 定义你的model和它的属性
const CarSchema = {
name: 'Car',
properties: {
make: 'string',
model: 'string',
miles: {type: 'int', default: 0},
}
};
const PersonSchema = {
name: 'Person',
properties: {
name: 'string',
birthday: 'date',
cars: {type: 'list', objectType: 'Car'},
picture: {type: 'data', optional: true}, // 可选属性
}
};

Realm.open({schema: [CarSchema, PersonSchema]})
.then(realm => {
// 创建Realm对象,并且存储到local storage中
realm.write(() => {
const myCar = realm.create('Car', {
make: 'Honda',
model: 'Civic',
miles: 1000,
});
myCar.miles += 20; // 更新一个属性值
});

// Query Realm for all cars with a high mileage
const cars = realm.objects('Car').filtered('miles > 1000');

// 将会带着一个我们之前创建的car返回一个结果对象
cars.length // => 1

// Add another car
realm.write(() => {
const myCar = realm.create('Car', {
make: 'Ford',
model: 'Focus',
miles: 2000,
});
});

// 查询结果是实时更新的
cars.length // => 2
});

请注意,如果你将要在server-side/node使用Realm,可以通过Realm对象服务查看一些额外信息。

开始

安装

跟着以下步骤通过npm来安装Realm Javascript,或者在Github查看源码

React Native

先决条件

  • 确保你已经准备好React Native运行环境,按照React Native指引来开始;

  • 使用了Realm的应用程序需要可以在IOS和Android平台运行;

  • React Native 版本必须大于等于 0.31.0

安装

  • 创建一个React Native项目:

    1
    react-native init <project-name>
  • 进入新创建的项目内并且添加realm依赖:

    1
    2
    3
    4
    ## npm 安装
    npm install --save realm
    ## yarn 安装
    yarn add realm

接下来,将你的项目链接到Realm的本地模板:

1
react-native link realm

Android警告:由于版本关系,react-native link可能会生成一个无效的配置,能够正确更新Gradle(android/settings.gradleandroid/app/build.gradle)但是添加Realm模块失败。请确认react-native link正确的添加了Realm模块;如果没有的话,按照下列步骤手动添加到库:

  1. 添加下列的代码到android/settings.gradle

    1
    2
    include ':realm'
    project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android')
  2. android/app/build.gradle文件中,将Realm库编译行添加到依赖:

    1
    2
    3
    dependencies {
    compile project(':realm')
    }
  3. MainApplication.java中添加引用并且连接包:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import io.realm.react.RealmReactPackage; // 增加这个引入

    public class MainApplication extends Application implements ReactApplication {
    @Override
    protected List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(
    new MainReactPackage(),
    new RealmReactPackage() // 增加这一行
    );
    }
    }

你现在已经准备完毕。想要实践下Realm,在index.ios.jsindex.android.js内将class <project-name>类定义替换成以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const Realm = require('realm');

class <project-name> extends Component {
constructor(props) {
super(props);
this.state = { realm: null };
}

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

render() {
const info = this.state.realm
? 'Number of dogs in this Realm: ' + this.state.realm.objects('Dog').length
: 'Loading...';

return (
<View style={styles.container}>
<Text style={styles.welcome}>
{info}
</Text>
</View>
);
}
}

你可以在设备或者模拟器上运行你的代码。

Node.js

这些只是指导安装Realm Node.js SDK开发者版本。如果你已经下载了专业版或者企业版,按照你收到邮件的安装指导进行。

简单的使用npm包管理器来安装realm:

1
npm install --save realm

想要使用SDK,在你的项目中require('realm')

1
var Realm = require('realm');

样例

可以在Github的realm-js库中找到。

在Android中要注意,你需要安装有NDK并且设置了ANDROID_NDK的环境变量。

1
export ANDROID_NDK=/usr/local/Cellar/android-ndk/r10e

获取帮助

模块

Realm 数据 models 通过 schema 信息定义,在Realm初始化期间将定义传入。
一个对象的schema定义包含了对象的名称和一系列的属性,每个属性都有它自己的名字和类型;同时也可以将属性定义为一个object或者list,引用其他对象类型。你也可以指定每个属性是否可选或者是否有个默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const Realm = require('realm');

const CarSchema = {
name: 'Car',
properties: {
make: 'string',
model: 'string',
miles: {type: 'int', default: 0},
}
};
const PersonSchema = {
name: 'Person',
properties: {
name: 'string',
birthday: 'date',
cars: {type: 'list', objectType: 'Car'},
picture: {type: 'data', optional: true}, // 可选属性
}
};

// 附带Car和Person models初始化Realm
Realm.open({schema: [CarSchema, PersonSchema]})
.then(realm => {
// ... 使用realm实例来读取或者修改数据
})

Classes

在这点上,通过类定义模型的支持是有限的。它能够在React Native上生效,但是在Node中不起作用。

如果你想要使用ES2015样式的类(也有可能需要继承存在的一些特性),你只需要在构造器中定义schema就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person {
get fullName() {
return this.firstName + ' ' + this.lastName;
}
}

Person.schema = {
name: 'Person',
properties: {
firstName: {type: 'string'},
lastName: {type: 'string'}
}
};

你现在就可以将类本身传递到open配置中的schema属性内:

1
2
Realm.open({schema: [Person]})
.then( /* ... */ );

你可以一直使用以下方法访问属性:

1
2
3
4
5
6
7
8
realm.write(() => {
const john = realm.create('Person', {
firstName: 'John',
lastName: 'Smith'
});
john.lastName = 'Peterson';
console.log(john.fullName); // -> 'John Peterson'
});

支持的类型

Realm支持以下基本类型:bool, int, float, double, string, datadate

  • bool 属性对应 JavaScript Bool 类型
  • int, floatdouble 属性对应 JavaScript Number 类型. 另外 ‘int’ 和 ‘double’ 以64位存储; float 以32位存储.
  • string 属性对应 String 类型
  • data 属性对应 ArrayBuffer 类型
  • date 属性对应 Date 类型

指定一个基础类型的简写,你只需要指定一个类型而不需要指定一个含有单个键值对的对象:

1
2
3
4
5
6
7
8
const CarSchema = {
name: 'Car',
properties: {
// 以下属性类型是等价的
make: {type: 'string'},
model: 'string',
}
}

关系

对一关系

通过将你关联对象的name属性作为主对象的属性类型,来形成对一关系:

1
2
3
4
5
6
7
8
const PersonSchema = {
name: 'Person',
properties: {
// 以下的两个属性是等价的
car: {type: 'Car'},
van: 'Car',
}
};

当时用了对象作为属性之后,你需要确保所有关联的对象已经在开启Realm的时候声明。

1
2
3
// 因为PersonSchema包含了一个类型为Car的属性,所以CarSchema也需要在此声明
Realm.open({schema: [CarSchema, PersonSchema]})
.then(/* ... */);

当访问对象属性的时候,你可以以普通的属性访问语法访问对象属性内部的属性:

1
2
3
4
5
6
7
8
9
10
realm.write(() => {
const nameString = person.car.name;
person.car.miles = 1100;

// create a new Car by setting the property to valid JSON
person.van = {make: 'Ford', model: 'Transit'};

// set both properties to the same car instance
person.car = person.van;
});

对多关系

对于对多属性,你必须声明这个属性类型为list类型,并且必须同时声明这个listitem类型:

1
2
3
4
5
6
const PersonSchema = {
name: 'Person',
properties: {
cars: {type: 'list', objectType: 'Car'},
}
}

当访问list属性的时候,将会返回List对象。List对象拥有跟正常Javascript对象类似的方法(需要注意的是:typeof List仍旧是对象类型,而不是Array)。跟普通JS数组对象最大的区别是,对List的任意更改都会自动持久化到Realm下。另外,List属于底层对象的基础属性,你只能通过从主对象访问属性来获取List实例,它们不允许被手动创建。

1
2
3
4
5
6
7
8
9
let carList = person.cars;

// 增加新的car到列表
realm.write(() => {
carList.push({make: 'Honda', model: 'Accord', miles: 100});
carList.push({make: 'Toyota', model: 'Prius', miles: 200});
});

let secondCar = carList[1].model; // 通过数组索引访问

反向关系

关系是单向的。所以,当一个对多属性Person.dogs关联了Dog实例,一个对一属性Dog.owner关联了Person的同时,这两个联系对于互相来说是独立的。即添加一个DogPerson实例的dogs属性的时候,并不会自动更新dogs的owner属性。因为,手动关联关系对会造成容易出错、更加复杂化并且会产生重复信息的问题,所以Realm提供了链接对象属性来表示这些反向关系。

当链接对象属性的时候,你可以通过那个特定的属性,获取关联的所有对象。比如:一个dog对象可以有一个owners属性,其中有一个包含了这个dog对象的dogs属性。以上可以通过指定owners的属性类型为linkingObjects并且关联对象为Person对象来达成目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
const PersonSchema = {
name: 'Person',
properties: {
dogs: {type: 'list', objectType: 'Dog'},
}
}

const DogSchema = {
name:'Dog',
properties: {
owners: {type: 'linkingObjects', objectType: 'Person', property: 'dogs'}
}
}

一个linkingObjects属性可以关联到一个List属性(对多关系)或者一个Object属性(对一关系):

1
2
3
4
5
6
7
8
9
10
11
12
13
const ShipSchema = {
name: 'Ship',
properties: {
captain: 'Captain'
}
}

const CaptainSchema = {
name: 'Captain',
properties: {
ships: {type: 'linkingObjects', objectType: 'Ship', property: 'captain'}
}
}

当访问linkingObjects属性的时候,一个Results对象将会被返回,目前为止,这个对象完全支持查询和排序。linkingObject属性属于对象要求的基本属性,所以不能被设置值或者直接操作。它们会在一个事务完成后自动更新。

不通过schema访问linkingObjects:如果你在开启realm的时候,没有声明schema,举个例子,在一个 Realm Functions的回调里面,你可以调用一个对象实例的linkingObjects(objectType, property)方法获取linkingObjects属性。

1
2
let captain = realm.objectForPrimaryKey('Captain', 1);
let ships = captain.linkingObjects('Ship', 'captain');

Optional 属性

在你的属性定义中,一个属性可以通过optional标记被声明可选或者必填:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const PersonSchema = {
name: 'Person',
properties: {
name: {type: 'string'}, // 必填属性
birthday: {type: 'date', optional: true}, // 可选属性

// 对象属性被设置为可选(无法变更)
car: {type: 'Car'},
}
};

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

realm.write(() => {
// 在创建的时候,可选属性可以被设置值为null或者undefind
let charlie = realm.create('Person', {
name: 'Charlie',
birthday: new Date(1995, 11, 25),
car: null,
});

// 可选属性可以被设置值为`null`或者`undefind`
// 或者一个新的非null的值
charlie.birthday = undefined;
charlie.car = {make: 'Honda', model: 'Accord', miles: 10000};
});

可以从上面看出对象属性总是允许可选的,并不需要一个optional标记。List属性不能被声明为可选并且值不能设置为null。你可以通过设置或者初始化一个空的list来清空它。

默认值设置

在属性定义中,可以通过设置default标记来设置默认值。在对象创建期间,不给属性设置值将会使用默认值来填充。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const CarSchema = {
name: 'Car',
properties: {
make: {type: 'string'},
model: {type: 'string'},
drive: {type: 'string', default: 'fwd'},
miles: {type: 'int', default: 0}
}
};

realm.write(() => {
// 因为`miles`未被填写,所以会被默认设置为`0`
// 因为`drive`被指定值,它将会覆盖默认值
realm.create('Car', {make: 'Honda', model: 'Accord', drive: 'awd'});
});

索引属性

你可以在属性定义中增加indexed标记来使得整个属性被索引。支持int, stringbool类型的属性。

1
2
3
4
5
6
7
var BookSchema = {
name: 'Book',
properties: {
name: { type: 'string', indexed: true },
price: 'float'
}
};

索引一个属性以插入的更慢为代价,非常大的提升了查询的速度。

主键

在对象模型内,可以对intstring属性指定一个primaryKey属性。声明一个主键,可以使得查询和更新更有效率,并且将强制值唯一性。一旦一个带有主键的对象加入到Realm中,则这个主键无法变更。

1
2
3
4
5
6
7
8
9
const BookSchema = {
name: 'Book',
primaryKey: 'id',
properties: {
id: 'int', // 主键
title: 'string',
price: 'float'
}
};

主键属性将会自动被索引。

写入

在Realm内更改对象内容——创建、更新和删除——必须放在一个write()的事务块里面。需要注意的是写事务是一个不可忽略的开销;在你的代码中应该尽量减少write事务块。

创建对象

使用create方法创建对象:

1
2
3
4
5
6
7
try {
realm.write(() => {
realm.create('Car', {make: 'Honda', model: 'Accord', drive: 'awd'});
});
} catch (e) {
console.log("Error on creation");
}

注意:在write()内抛出任意异常都会取消事务。try/catch块并不会在所有样例代码上出现,但是捕获异常这是一个好的习惯。

内嵌对象

如果一个对象有一个对象属性,通过特定的JSON值,会递归创建那些属性值。

1
2
3
4
5
6
7
realm.write(() => {
realm.create('Person', {
name: 'Joe',
// nested objects are created recursively
car: {make: 'Honda', model: 'Accord', drive: 'awd'},
});
});

更新对象

类型更新

在写事务中你可以通过设置属性值来更新对象:

1
2
3
realm.write(() => {
car.miles = 1100;
});

根据主键创建和更新对象

如果你的模型类包含有主键,你可以让Realm智能的根据主键来创建和更新对象。这个功能可以通过在create方法传递第三个值为true来开启。

1
2
3
4
5
6
7
realm.write(() => {
// 创建一个book对象
realm.create('Book', {id: 1, title: 'Recipes', price: 35});

// 根据主键id更新price值
realm.create('Book', {id: 1, price: 55}, true);
});

在上述的案例中,因为已经存在了一个id值为1的对象并且我们传递了true值给第三个参数,那么price属性将会被更新,而不是尝试创建一个新的对象。由于name属性被省略,对象保留此属性的原始值。注意:当用主键创建或者更新对象的时候,主键必须被指定。

删除对象

write事务块内,对象可以通过delete方法删除。

1
2
3
4
5
6
7
8
9
10
11
12
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
});

查询

查询允许你可以通过Realm获取单类型对象,并且对这些对象进行过滤和排序。在Realm中所有的查询(包括查询和属性获取)都是懒加载形式。数据只会在对象或者属性被访问的时候才会读取。这样做可以让你在请求大数据量的时候有个好的加载体验。当查询的时候,将会返回Results对象。Results只是你的数据的一个简单展示,并且是不可变更的。

最基础的获取数据的方式是通过在realm中使用objects方法,以此获取到所给类型的所有对象:

1
let dogs = realm.objects('Dog'); // 从Realm中获取所有的'Dog'

过滤

你可以通过调用filtered方法附带查询字符串,来获取一个被过滤的结果对象Results

举个例子:以下的代码(附带了colortannameB开头)将会改变我们之前样例查询所有Dogs的结果:

1
2
let dogs = realm.objects('Dog');
let tanDogs = dogs.filtered('color = "tan" AND name BEGINSWITH "B"');

目前为止,在查询语句中,只有NSPredicate语法的子集才被支持。基本比较符==, !=, >, >=, <<=支持数字类属性。==, BEGINSWITH, ENDSWITH, 和 CONTAINS支持字符类属性。字符串类的比较可以通过添加[c]到比较符号(==[c], BEGINSWITH[c]等等)来忽略大小写。过滤关联的属性或者孩子对象,可以通过指定一个比如car.color == 'blue'的值路径来进行过滤。

排序

Results允许你指定基于单个或多个属性的排序标准和顺序。比如,下面的代码对样例上的返回结果根据miles做了数字排序。

1
2
3
4
let hondas = realm.objects('Car').filtered('make = "Honda"');

// 根据 mileage 排序 Honda
let sortedHondas = hondas.sorted('miles');

注意:Results的排序只有在查询排序时才能保持一致。为了行能考虑,插入的顺序并不被保证保留。

自动更新结果集

Results实例是实时的,自动更新视图到底层数据的,这意味着结果永远不必重新获取。修改查询的对象,将会立即反应到结果中。

1
2
3
4
5
6
7
let hondas = realm.objects('Car').filtered('make = "Honda"');
// hondas.length == 0

realm.write(() => {
realm.create('Car', {make: 'Honda', model: 'RSX'});
});
// hondas.length == 1

这对所有的Results实例都起作用,包括那些返回后又调用了objects, filteredsorted方法。

Results 不止保证Realm更快更有效率,且使得你的代码更简单更灵活。比如,如果你的试图依赖查询结果,你可以存储Results到一个属性并且不需要在每次访问这个视图前刷新试图的数据。

您可以订阅notifications,以了解Realm数据的更新时间,指明应用程序的UI应该如何刷新,而无需重新获取结果Results

限制结果

大多数的数据库技术都提供有分页的功能(例如MySQL的LIMIT关键字)。这个通常是为了避免非必要的大量磁盘读取或者一次性拉取太多的结果。

因为Realm的懒查询,做这种分页行为并不必要,因为Realm只会在真正访问这个属性的时候才会从Results实例中加载对象。

如果为了UI相关或者其他实现的理由需要结果集的某些特定的子结果集,就跟获取Results对象一样简单,就是指读出你想要的对象即可。

1
2
3
4
let cars = realm.objects('Car');

// 获取开头5个car对象
let firstCars = cars.slice(0, 5);

Realms

打开Realms

打开Realm只需要通过调用Realm类中的静态open方法来执行。传递一个配置对象。我们已经在使用包含schema键的配置对象中看到过这个例子:

1
2
3
4
5
6
7
8
// 获取支持我们对象的默认Realm
Realm.open({schema: [Car, Person]})
.then(realm => {
// ...use the realm instance here
})
.catch(error => {
// Handle the error here if something went wrong
});

关于配置对象完整细节,看这个API文档。其他更多配置项,除了schema,还有:

  • path: 指定一个给另一个Realm
  • migration: 一个迁移函数
  • sync:一个同步对象,用来同步打开与Realm对象服务器同步的Realm
  • inMemory: Realm将会在内存中打开,对象不会持久化到硬盘;一旦最后的Realm实例关闭,所有的对象将会不复存在

默认Realm

在之前的所有例子中,你可能已经注意到路径参数已被省略。在这种情况下,将会使用默认的Realm路径。但是你可以通过使用Realm.defaultPath这个全局属性来访问或者变更默认路径。

其他的Realms

有时候在不同的路径保存保存多个Realms是有用的。举个例子,除了你的主要Realm之外,你还可能需要将你的数据和应用打包在同一个Realm文件中。你可以通过初始化Realm的时候指定path参数来执行此操作。所有的路径都是相对于你的应用的可写目录:

1
2
3
4
5
// 在另一个位置打开Realm
Realm.open({
path: 'anotherRealm.realm',
schema: [CarSchema]
}).then(/* ... */);

Schema版本

另一个在打开Realm时可用的参数是schemaVersion。如果不设置,则默认为0。在使用与之前包含对象不同的Realm初始化现有Realm的时候,你需要指定schemaVersion的值。如果schema被更新了,但是没有指定schemaVersion,则会抛出异常:

1
2
3
4
5
6
7
8
9
const PersonSchema = {
name: 'Person',
properties: {
name: 'string'
}
};

// schemaVersion 默认值为0
Realm.open({schema: [PersonSchema]});

如果你在之后这么做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const UpdatedPersonSchema = { 
// schema的名称是相同的,所有之前在Realm内的Person对象将会被更新
name: ‘Person’,
properties: {
name: ‘string’,
dog: ‘Dog’ // 新属性
}
};

// 这将会抛出异常,因为schema已经被改变了但是schemaVersion未被指定
Realm.open({schema: [UpdatedPersonSchema]});

// 这个将会成功,并且更新schema为新的schema
Realm.open({schema: [UpdatedPersonSchema], schemaVersion: 1});

如果你希望取得一个Realm当前的schema版本,你可以使用Realm.schemaVersion方法。

1
const currentVersion = Realm.schemaVersion(Realm.defaultPath);

同步打开Realms

你可以通过简单的调用构造函数并将配置信息传递给它来创建一个realm的实例。这通常不被推荐,因为它阻塞了程序可能潜在的产生耗时操作,特别是当有schema迁移需要进行或者realm是同步的,并且你不希望冒在数据完全下载之前修改它的风险。

如果你仍旧想要这么做,形式很简单:

1
2
3
4
const realm = new Realm({schema: [PersonSchema]});

// 你现在可以访问realm实例了
realm.write(/* ... */);

注意:如果一个realm只有只读权限,那么你必须以异步的方式打开它。因为使用上述形式同步打开一个只读的realm,将会一个错误。

迁移

在使用数据库的时候,你的数据模型可能随着时间的变化随时发生改变。举个例子,假设我们有一个Person的模型:

1
2
3
4
5
6
7
8
const PersonSchema = {
name: 'Person',
properties: {
firstName: 'string',
lastName: 'string',
age: 'int'
}
}

我们想要更新数据模型,只需要一个name属性而不是将它拆分成姓氏和名称两个属性。为此,我们只需要将schema更改成以下内容:

1
2
3
4
5
6
7
const PersonSchema = {
name: 'Person',
properties: {
name: 'string',
age: 'int'
}
}

在这点上,如果你以前的模型版本上保存了任意的数据,新的代码与Realm存储在磁盘上的旧数据将不匹配。当这种情况发生的时候,当你用新的schema打开之前的Realm的时候,将会抛出一个异常,除非你进行数据迁移。

进行迁移

你可以通过更新schemaVersion并定义一个可选的migration函数来定义一个迁移和与之关联的schema版本。你的迁移函数逻辑需要能够支持将之前的schema版本的数据模型转化为新schema的数据模型。当打开Realm的时候,只有当需要迁移的时候,才会将Realm更新到给定版本的schema。

如果没有提供迁移功能,则当更新到新的schemaVersion的时候,新的属性将会添加而旧的属性将会从数据库删除。如果您需要在升级版本时更新旧的或者填充的新属性,那么可以在迁移功能中执行此操作。例如,假设我们想要迁移早先声明的Person模型,你可以使用旧的firstNamelastName的数据填充新的name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Realm.open({
schema: [PersonSchema],
schemaVersion: 1,
migration: (oldRealm, newRealm) => {
// 只有当schemaVersion更新为1的时候才应用这个更改
if (oldRealm.schemaVersion < 1) {
const oldObjects = oldRealm.objects('Person');
const newObjects = newRealm.objects('Person');

// 循环所有的对象,并且在新的schema中设置name属性
for (let i = 0; i < oldObjects.length; i++) {
newObjects[i].name = oldObjects[i].firstName + ' ' + oldObjects[i].lastName;
}
}
}
}).then(realm => {
const fullName = realm.objects('Person')[0].name;
});

一旦你迁移完全成功,所有Realm对象都可以像往常一样被使用。

线性迁移

在使用上述迁移模式的时候,你可能会在多版本的迁移上出现问题。比方用户跳过应用程序的更新,并且在跳过的版本中该属性已被更改多次,则发生这种情况可能会发生。在这种情况下,您可能需要编辑旧的迁移代码,以将数据从旧的schema下更新到最新的schema。

可以通过顺序运行多个迁移来避免这个问题,确保数据库根据版本依次升级并且与之关联的每个升级迁移都被执行。按照这个步骤的话,则旧的迁移代码不必被修改,尽管你需要为将来的使用保存所有旧的schema和迁移语句。一个类似的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const schemas = [
{ schema: schema1, schemaVersion: 1, migration: migrationFunction1 },
{ schema: schema2, schemaVersion: 2, migration: migrationFunction2 },
...
]

// 第一个schema将要更新的是当前的schema版本
// 因为第一个schema是在我们的数组中的第一个位置
let nextSchemaIndex = Realm.schemaVersion(Realm.defaultPath);
while (nextSchemaIndex < schemas.length) {
const migratedRealm = new Realm(schemas[nextSchemaIndex++]);
migratedRealm.close();
}

// 用最新的schema打开Realm
Realm.open(schemas[schemas.length-1]);

通知

Realm, ResultsList对象提供了addListener方法来注册通知回调。无论对象什么时候更新,数据更改通知的回调都会被触发。

有两种通知方式:“Realm 通知”(当写事务被提交后简单的回调会被通知)和“集合通知”(更复杂的回调可以在插入、删除和更新的时候接收到更改的元数据)。

另外,此外,专业版和企业版提供事件处理通知。 阅读“Realm移动平台”了解更多信息。

Realm通知

Realm实例在每次写事务提交后都会发送通知给其他实例。注册通知:

1
2
3
4
5
6
7
8
9
10
11
12
function updateUI() {
// ...
}

// 监听Realm通知
realm.addListener('change', updateUI);

// ..之后删除监听
realm.removeListener('change', updateUI);

// ..或者删除所有监听
realm.removeAllListeners();

集合通知

集合通知包含了描述在细粒度级别上发生了哪些更改的信息。这包括自上次通知以后,那些被插入、删除或者修改过的对象的索引。集合通知是异步推送:首先使用初始结果,然后在任何修改集合中的任何对象的写事务之后,从集合中删除对象,或向集合中添加新对象。

当这些改变发生的时候,addListener的通知回调函数将接收到两个参数。第一个是集合已经被更改,第二个是changes对象,包含了受删除、插入和修改影响的集合索引。

前两项,删除和插入,每当对象加入集合或从集合中被删除的时候,都会记录索引。每当你添加对象到Realm或者将其从Realm中删除的时候,都需要考虑到这点。

// TODO 量有点大,之后的内容待完善