前言
- 因?yàn)?實(shí)戰(zhàn)項(xiàng)目系列 涉及到數(shù)據(jù)持久化,這邊就來補(bǔ)充一下。
- 如本文有錯(cuò)或理解偏差歡迎聯(lián)系我,會(huì)盡快改正更新!
- 如有什么問題,也可直接通過郵箱 277511806@qq.com 聯(lián)系我。
- demo鏈接: https://pan.baidu.com/s/1hsspiio 密碼: dk3h
數(shù)據(jù)持久化
數(shù)據(jù)持久化一直都是軟件開發(fā)中重要的一個(gè)環(huán)節(jié),幾乎所有的應(yīng)用都具備這一項(xiàng)功能;那什么是數(shù)據(jù)持久化呢?—— 說白了就是數(shù)據(jù)的本地化存儲(chǔ),將數(shù)據(jù)存儲(chǔ)到本地,在需要的時(shí)候進(jìn)行調(diào)用。
-
這邊我們介紹兩種在 React-Native 中比較常用的存儲(chǔ)方式
- AsyncStorage:這是官方使用的存儲(chǔ)方式,類似于 iOS 中的
NSUserDefault,區(qū)別在于,AsyncStorage只能存儲(chǔ) 字符串鍵值對(duì),而NSUserDefault可以存儲(chǔ) 字符串和number。 - Realm:今天才發(fā)現(xiàn) Realm 也已經(jīng)支持 React-Native ,這是新興的移動(dòng)端數(shù)據(jù)存儲(chǔ)方式,在沒有它之前,一直都是使用 sqlist 進(jìn)行數(shù)據(jù)存儲(chǔ),在性能上,各有優(yōu)勢(shì),但是操作上,Realm 有著明顯優(yōu)勢(shì),更方便使用。
- AsyncStorage:這是官方使用的存儲(chǔ)方式,類似于 iOS 中的
接下來我們就來看看怎么使用它們。
AsyncStorage 簡(jiǎn)單使用
AsyncStorage方法官方文檔寫得很詳細(xì),這邊就不對(duì)贅述了!
AsyncStorage 使用方法很簡(jiǎn)單,我們就直接上代碼:
// 增加
createData() {
AsyncStorage.setItem('name', JSON.stringify('吉澤明步'), (error, result) => {
if (!error) {
this.setState({
data:'保存成功!'
})
}
});
}
// 查詢
inquireData() {
AsyncStorage.getItem('name')
.then((value) => {
let jsonValue = JSON.parse((value));
this.setState({
data:jsonValue
})
})
}
// 更新
upData() {
AsyncStorage.setItem('name', JSON.stringify('蒼井空'), (error, result) => {
if (!error) {
this.setState({
data:'更新成功!'
})
}
});
}
// 刪除
removeData() {
AsyncStorage.removeItem('name');
this.setState({
data:'刪除完成!'
})
}

按照官方推薦,我們使用 AsyncStorage 前,最好進(jìn)行一層封裝,React-Native中文網(wǎng) 給我們提供了一個(gè)比較好的框架 —— react-native-storage,我們可以直接使用它,方法很簡(jiǎn)單,說明文檔中說得很詳細(xì)。
既然是第三方框架,那么第一部肯定就是導(dǎo)入到我們的工程中:
npm install react-native-storage --save
- 接著,我們根據(jù)創(chuàng)建一個(gè)
Storage文件專門對(duì)框架進(jìn)行初始化操作:
import {
AsyncStorage,
} from 'react-native';
// 第三方框架
import Storage from 'react-native-storage';
var storage = new Storage({
// 最大容量,默認(rèn)值1000條數(shù)據(jù)循環(huán)存儲(chǔ)
size: 1000,
// 存儲(chǔ)引擎:對(duì)于RN使用AsyncStorage,對(duì)于web使用window.localStorage
// 如果不指定則數(shù)據(jù)只會(huì)保存在內(nèi)存中,重啟后即丟失
storageBackend: AsyncStorage,
// 數(shù)據(jù)過期時(shí)間,默認(rèn)一整天(1000 * 3600 * 24 毫秒),設(shè)為null則永不過期
defaultExpires: 1000 * 3600 * 24,
// 讀寫時(shí)在內(nèi)存中緩存數(shù)據(jù)。默認(rèn)啟用。
enableCache: true,
// 如果storage中沒有相應(yīng)數(shù)據(jù),或數(shù)據(jù)已過期,
// 則會(huì)調(diào)用相應(yīng)的sync方法,無縫返回最新數(shù)據(jù)。
// sync方法的具體說明會(huì)在后文提到
// 你可以在構(gòu)造函數(shù)這里就寫好sync的方法
// 或是寫到另一個(gè)文件里,這里require引入
// 或是在任何時(shí)候,直接對(duì)storage.sync進(jìn)行賦值修改
sync: require('./sync')
})
// 全局變量
global.storage = storage;
到這里,我們需要注意的就是要在哪里初始化這個(gè)文件,其實(shí)一個(gè)思路就是 —— 在哪個(gè)地方,我們只需要引用一次文件,就可以在其他文件中使用(比如:我們程序默認(rèn)的進(jìn)口就是
index.ios/android.js文件,那么只要在他們中引用一次文件即可,這樣就不需要去注意什么調(diào)用順序,因?yàn)?index.ios/android.js文件肯定是最先調(diào)用的,它們才是真正的王)。然而,為了方便我們使用同一套代碼,我們會(huì)創(chuàng)建一個(gè)
Main文件作為程序入口的中轉(zhuǎn)總站來管理其他的文件,然后外界只要調(diào)用這個(gè)Main文件,就可以展示里面的所有東西。所以,將引用放到Main文件中是最好的選擇。
// 在 main 文件中添加
import storage from '封裝的文件位置';
到這里,我們就完成了最基礎(chǔ)的配置,我們只需要在需要用到的地方直接使用就可以了,首先我們?cè)谛陆ㄒ粋€(gè)文件,然后從Main文件跳轉(zhuǎn)到這個(gè)文件中。
接著,我們就真正地自己來使用一下這個(gè)框架:
// 增加
createData() {
// 使用key保存數(shù)據(jù)
storage.save({
key:'storageTest', // 注意:請(qǐng)不要在key中使用_下劃線符號(hào)!
rawData: {
name:'吉澤明步',
city:'xx省xxx市'
},
// 設(shè)為null,則不過期,這里會(huì)覆蓋初始化的時(shí)效
expires: 1000 * 3600
});
}
// 查詢
inquireData() {
storage.load({
key:'storageTest',
// autoSync(默認(rèn)為true)意味著在沒有找到數(shù)據(jù)或數(shù)據(jù)過期時(shí)自動(dòng)調(diào)用相應(yīng)的sync方法
autoSync: true,
// syncInBackground(默認(rèn)為true)意味著如果數(shù)據(jù)過期,
// 在調(diào)用sync方法的同時(shí)先返回已經(jīng)過期的數(shù)據(jù)。
// 設(shè)置為false的話,則始終強(qiáng)制返回sync方法提供的最新數(shù)據(jù)(當(dāng)然會(huì)需要更多等待時(shí)間)。
syncInBackground: true,
// 你還可以給sync方法傳遞額外的參數(shù)
syncParams: {
extraFetchOptions: {
// 各種參數(shù)
},
someFlag: true,
},
}).then(ret => {
// 如果找到數(shù)據(jù),則在then方法中返回
// 注意:這是異步返回的結(jié)果(不了解異步請(qǐng)自行搜索學(xué)習(xí))
// 你只能在then這個(gè)方法內(nèi)繼續(xù)處理ret數(shù)據(jù)
// 而不能在then以外處理
// 也沒有辦法“變成”同步返回
// 你也可以使用“看似”同步的async/await語(yǔ)法
// 更新data值
this.setState({
data: ret.name
});
}).catch(err => {
//如果沒有找到數(shù)據(jù)且沒有sync方法,
//或者有其他異常,則在catch中返回
console.warn(err.message);
switch (err.name) {
case 'NotFoundError':
// 更新
this.setState({
data:'數(shù)據(jù)為空'
});
break;
case 'ExpiredError':
// TODO
break;
}
})
}
// 更新
upData() {
// 重新存儲(chǔ)即可
storage.save({
key:'storageTest', // 注意:請(qǐng)不要在key中使用_下劃線符號(hào)!
rawData: {
name:'蒼井空',
city:'xx省xxx市'
},
// 設(shè)為null,則不過期,這里會(huì)覆蓋初始化的時(shí)效
expires: 1000 * 3600
});
}
// 刪除
removeData() {
// 刪除單個(gè)數(shù)據(jù)
storage.remove({
key: 'storageTest'
});
// storage.remove({
// key: 'react-native-storage-test',
// name:'吉澤明步'
// });
// // !! 清空map,移除所有"key-id"數(shù)據(jù)(但會(huì)保留只有key的數(shù)據(jù))
// storage.clearMap();
//
// // 獲取某個(gè)key下的所有id
// storage.getIdsForKey('user').then(ids => {
// console.log(ids);
// });
//
// // 獲取某個(gè)key下的所有數(shù)據(jù)
// storage.getAllDataForKey('user').then(users => {
// console.log(users);
// });
//
// // !! 清除某個(gè)key下的所有數(shù)據(jù)
// storage.clearMapForKey('user');
}

- 很簡(jiǎn)單對(duì)不,那對(duì)于 react-native-storage 的使用就先講到這里。
Realm 配置與常見錯(cuò)誤處理
很驚喜,Realm 也支持了 React-Native ,這樣我們可以在移動(dòng)端
愉快地進(jìn)行存儲(chǔ)操作了。而且使用方法 Realm 官方提供的文檔都一如既往地詳細(xì),所以如果感興趣,也可以到 Realm說明文檔 進(jìn)行學(xué)習(xí)(不知是網(wǎng)絡(luò)問題還是官方?jīng)]有整理好,我這邊中文版文檔是打不開的,所以只能看英文版),這邊我們直接將里面常用到的內(nèi)容整理出來,簡(jiǎn)單說下怎么使用。
首先,一樣還是需要打開終端將 Realm 放到我們的工程中
npm install --save realm
-
接著,添加 Realm 與 工程的鏈接
- React-Native >= 0.31.0
react-native link realm- React-Native < 0.31.0
rnpm link realm

- 出現(xiàn)上面的提示表示成功,然后我們需要卸載模擬器中已經(jīng)安裝的
APP并重新安裝(Xcode會(huì)進(jìn)行一系列配置,其中會(huì)在網(wǎng)絡(luò)下載一下必要的組件,時(shí)間視網(wǎng)絡(luò)情況而定),來測(cè)試下安卓和iOS,2端是否能正常使用

-
如果出現(xiàn)有
err!等字樣或者在安卓中出現(xiàn)錯(cuò)誤警告,說明安卓端沒有成功地進(jìn)行全部配置,需要我們手動(dòng)進(jìn)行配置,步驟如下:-
如果出現(xiàn)
android Missing Realm constructor - please ensure RealmReact framework is included報(bào)錯(cuò):- 在
MainApplication中添加
new RealmReactPackage() - 在
-
如果還是鏈接不上,我們檢查以下幾處代碼是否有自動(dòng)添加
- settings.gradle 中是否有下面代碼,不存在手動(dòng)添加
include ':realm' project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android')- 如果還不行,到app => build.gradle 中是否有下面代碼,不存在手動(dòng)添加
dependencies { compile project(':realm') // 是否存在,不存在手動(dòng)添加(再舊版本有效,新版本不需要添加此項(xiàng)) compile fileTree(dir: "libs", include: ["*.jar"]) compile "com.android.support:appcompat-v7:23.0.1" compile "com.facebook.react:react-native:+" // From node_modules }
-
接著,重新運(yùn)行安卓:
react-native run-android
- 如果還是不行,可聯(lián)系官方,或者將錯(cuò)誤代碼發(fā)送給我,也許可以幫忙解決。
Realm 常用操作
- 作為數(shù)據(jù)庫(kù),使用它無法就是
增刪改查這老四樣,使用之前,還是老規(guī)矩,初始化表格:- name:表格名稱。
- primaryKey:主鍵,這個(gè)屬性的類型可以是 'int' 和 'string',并且如果設(shè)置主鍵之后,在更新和設(shè)置值的時(shí)候這個(gè)值必須保持唯一性,并且無法修改。
- properties:這個(gè)屬性內(nèi)放置我們需要的字段。
// 新建表模型
const PersonSchema = {
name: 'Person',
primaryKey:'id', // 官方?jīng)]給出自增長(zhǎng)的辦法,而且一般不會(huì)用到主鍵,這也解決了重復(fù)訪問的問題,而且實(shí)際開發(fā)中我們不需要主鍵的,讓服務(wù)端管就是了
properties: {
id:'int',
name: 'string',
tel_number: {type: 'string', default: '156xxxxxxxx'}, // 添加默認(rèn)值的寫法
city: 'string' // 直接賦值的方式設(shè)置類型
}
};
- 初始化 Realm:
// 根據(jù)提供的表初始化 Realm,可同時(shí)往數(shù)組中放入多個(gè)表
let realm = new Realm({schema: [PersonSchema]});
- 增加數(shù)據(jù):
// 增加
createData() {
realm.write(() => {
realm.create('Person', {id:0, name:'吉澤明步', tel_number:'137xxxxxxxx', city:'xx省xx市xxxxxx'});
realm.create('Person', {id:1, name:'蒼井空', tel_number:'137xxxxxxxx', city:'xx省xx市xxxxxx'});
realm.create('Person', {id:2, name:'小澤瑪利亞', tel_number:'137xxxxxxxx', city:'xx省xx市xxxxxx'});
realm.create('Person', {id:3, name:'皮皮蝦我們走', tel_number:'137xxxxxxxx', city:'xx省xx市xxxxxx'});
realm.create('Person', {id:4, name:'波多野結(jié)衣', tel_number:'137xxxxxxxx', city:'xx省xx市xxxxxx'});
})
}
-
查詢數(shù)據(jù)
- 查詢所有數(shù)據(jù):
// 查詢所有數(shù)據(jù) let persons = realm.objects('Person'); console.log ('name:' + persons[0].name + 'city:' + persons[0].city)- 根據(jù)條件查詢數(shù)據(jù)
// 查詢 inquireData() { let allData; // 獲取Person對(duì)象 let Persons = realm.objects('Person'); // 遍歷表中所有數(shù)據(jù) for (let i = 0; i<Persons.length; i++) { let tempData = '第' + i + '個(gè)' + Persons[i].name + Persons[i].tel_number + Persons[i].city + '\n'; allData += tempData } this.setState({ data:allData }) } // 根據(jù)條件查詢 filteredData() { let allData; // 獲取Person對(duì)象 let Persons = realm.objects('Person'); // 設(shè)置篩選條件 let person = Persons.filtered('id == 1'); if (person) { // 遍歷表中所有數(shù)據(jù) for (let i = 0; i<person.length; i++) { let tempData = '第' + (person[i].id + 1) + '個(gè)數(shù)據(jù):' + person[i].name + person[i].tel_number + person[i].city + '\n'; allData += tempData } } this.setState({ data:'篩選到的數(shù)據(jù):' + allData }) } 更新數(shù)據(jù):
// 更新
upData() {
realm.write(() => {
// 方式一
realm.create('Person', {id: 0, name: '皮皮蝦,我們走', tel_number: '156xxxxxxxx', city: 'xx省xx市xxxxxx'}, true);
// // 方式二:如果表中沒有主鍵,那么可以通過直接賦值更新對(duì)象
// // 獲取Person對(duì)象
// let Persons = realm.objects('Person');
// // 設(shè)置篩選條件
// let person = Persons.filtered('name == 蒼井空');
// // 更新數(shù)據(jù)
// person.name = '黃鱔門'
})
}
- 刪除數(shù)據(jù):
// 刪除
removeData() {
realm.write(() => {
// 獲取Person對(duì)象
let Persons = realm.objects('Person');
// 刪除
realm.delete(Persons);
})
}
