前言 因為 "實戰項目系列" 涉及到數據持久化,這邊就來補充一下。 如本文有錯或理解偏差歡迎聯繫我,會儘快改正更新! 如有什麼問題,也可直接通過郵箱 [email protected] 聯繫我。 demo鏈接: https://pan.baidu.com/s/1hsspiio 密碼: dk3h 數據持 ...
前言
- 因為 實戰項目系列 涉及到數據持久化,這邊就來補充一下。
- 如本文有錯或理解偏差歡迎聯繫我,會儘快改正更新!
- 如有什麼問題,也可直接通過郵箱 [email protected] 聯繫我。
- demo鏈接: https://pan.baidu.com/s/1hsspiio 密碼: dk3h
數據持久化
數據持久化一直都是軟體開發中重要的一個環節,幾乎所有的應用都具備這一項功能;那什麼是數據持久化呢?—— 說白了就是數據的本地化存儲,將數據存儲到本地,在需要的時候進行調用。
- 這邊我們介紹兩種在 React-Native 中比較常用的存儲方式
- AsyncStorage:這是官方使用的存儲方式,類似於 iOS 中的
NSUserDefault
,區別在於,AsyncStorage
只能存儲 字元串鍵值對,而NSUserDefault
可以存儲 字元串和number。 - Realm:今天才發現 Realm 也已經支持 React-Native ,這是新興的移動端數據存儲方式,在沒有它之前,一直都是使用 sqlist 進行數據存儲,在性能上,各有優勢,但是操作上,Realm 有著明顯優勢,更方便使用。
- AsyncStorage:這是官方使用的存儲方式,類似於 iOS 中的
接下來我們就來看看怎麼使用它們。
AsyncStorage 簡單使用
AsyncStorage方法官方文檔寫得很詳細,這邊就不對贅述了!
AsyncStorage 使用方法很簡單,我們就直接上代碼:
// 增加
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 前,最好進行一層封裝,React-Native中文網 給我們提供了一個比較好的框架 —— react-native-storage,我們可以直接使用它,方法很簡單,說明文檔中說得很詳細。
既然是第三方框架,那麼第一部肯定就是導入到我們的工程中:
npm install react-native-storage --save
- 接著,我們根據創建一個
Storage
文件專門對框架進行初始化操作:
import {
AsyncStorage,
} from 'react-native';
// 第三方框架
import Storage from 'react-native-storage';
var storage = new Storage({
// 最大容量,預設值1000條數據迴圈存儲
size: 1000,
// 存儲引擎:對於RN使用AsyncStorage,對於web使用window.localStorage
// 如果不指定則數據只會保存在記憶體中,重啟後即丟失
storageBackend: AsyncStorage,
// 數據過期時間,預設一整天(1000 * 3600 * 24 毫秒),設為null則永不過期
defaultExpires: 1000 * 3600 * 24,
// 讀寫時在記憶體中緩存數據。預設啟用。
enableCache: true,
// 如果storage中沒有相應數據,或數據已過期,
// 則會調用相應的sync方法,無縫返回最新數據。
// sync方法的具體說明會在後文提到
// 你可以在構造函數這裡就寫好sync的方法
// 或是寫到另一個文件里,這裡require引入
// 或是在任何時候,直接對storage.sync進行賦值修改
sync: require('./sync')
})
// 全局變數
global.storage = storage;
到這裡,我們需要註意的就是要在哪裡初始化這個文件,其實一個思路就是 —— 在哪個地方,我們只需要引用一次文件,就可以在其他文件中使用(比如:我們程式預設的進口就是
index.ios/android.js
文件,那麼只要在他們中引用一次文件即可,這樣就不需要去註意什麼調用順序,因為index.ios/android.js
文件肯定是最先調用的,它們才是真正的王)。然而,為了方便我們使用同一套代碼,我們會創建一個
Main
文件作為程式入口的中轉總站
來管理其他的文件,然後外界只要調用這個Main
文件,就可以展示裡面的所有東西。所以,將引用
放到Main
文件中是最好的選擇。
// 在 main 文件中添加
import storage from '封裝的文件位置';
到這裡,我們就完成了最基礎的配置,我們只需要在需要用到的地方直接使用就可以了,首先我們在新建一個文件,然後從Main文件跳轉到這個文件中。
接著,我們就真正地自己來使用一下這個框架:
// 增加
createData() {
// 使用key保存數據
storage.save({
key:'storageTest', // 註意:請不要在key中使用_下劃線符號!
rawData: {
name:'吉澤明步',
city:'xx省xxx市'
},
// 設為null,則不過期,這裡會覆蓋初始化的時效
expires: 1000 * 3600
});
}
// 查詢
inquireData() {
storage.load({
key:'storageTest',
// autoSync(預設為true)意味著在沒有找到數據或數據過期時自動調用相應的sync方法
autoSync: true,
// syncInBackground(預設為true)意味著如果數據過期,
// 在調用sync方法的同時先返回已經過期的數據。
// 設置為false的話,則始終強制返回sync方法提供的最新數據(當然會需要更多等待時間)。
syncInBackground: true,
// 你還可以給sync方法傳遞額外的參數
syncParams: {
extraFetchOptions: {
// 各種參數
},
someFlag: true,
},
}).then(ret => {
// 如果找到數據,則在then方法中返回
// 註意:這是非同步返回的結果(不瞭解非同步請自行搜索學習)
// 你只能在then這個方法內繼續處理ret數據
// 而不能在then以外處理
// 也沒有辦法“變成”同步返回
// 你也可以使用“看似”同步的async/await語法
// 更新data值
this.setState({
data: ret.name
});
}).catch(err => {
//如果沒有找到數據且沒有sync方法,
//或者有其他異常,則在catch中返回
console.warn(err.message);
switch (err.name) {
case 'NotFoundError':
// 更新
this.setState({
data:'數據為空'
});
break;
case 'ExpiredError':
// TODO
break;
}
})
}
// 更新
upData() {
// 重新存儲即可
storage.save({
key:'storageTest', // 註意:請不要在key中使用_下劃線符號!
rawData: {
name:'蒼井空',
city:'xx省xxx市'
},
// 設為null,則不過期,這裡會覆蓋初始化的時效
expires: 1000 * 3600
});
}
// 刪除
removeData() {
// 刪除單個數據
storage.remove({
key: 'storageTest'
});
// storage.remove({
// key: 'react-native-storage-test',
// name:'吉澤明步'
// });
// // !! 清空map,移除所有"key-id"數據(但會保留只有key的數據)
// storage.clearMap();
//
// // 獲取某個key下的所有id
// storage.getIdsForKey('user').then(ids => {
// console.log(ids);
// });
//
// // 獲取某個key下的所有數據
// storage.getAllDataForKey('user').then(users => {
// console.log(users);
// });
//
// // !! 清除某個key下的所有數據
// storage.clearMapForKey('user');
}
- 很簡單對不,那對於 react-native-storage 的使用就先講到這裡。
Realm 配置與常見錯誤處理
很驚喜,Realm 也支持了 React-Native ,這樣我們可以在移動端
愉快地
進行存儲操作了。而且使用方法 Realm 官方提供的文檔都一如既往地詳細,所以如果感興趣,也可以到 Realm說明文檔 進行學習(不知是網路問題還是官方沒有整理好,我這邊中文版文檔是打不開的,所以只能看英文版),這邊我們直接將裡面常用到的內容整理出來,簡單說下怎麼使用。
首先,一樣還是需要打開終端將 Realm 放到我們的工程中
npm install --save realm
- 接著,添加 Realm 與 工程的鏈接
- React-Native >= 0.31.0
react-native link realm
- React-Native < 0.31.0
rnpm link realm
- 出現上面的提示表示成功,然後我們需要卸載模擬器中已經安裝的
APP
並重新安裝(Xcode會進行一系列配置,其中會在網路下載一下必要的組件,時間視網路情況而定),來測試下安卓和iOS,2端是否能正常使用
- 如果出現有
err!
等字樣或者在安卓中出現錯誤警告,說明安卓端沒有成功地進行全部配置,需要我們手動進行配置,步驟如下:- 如果出現
android Missing Realm constructor - please ensure RealmReact framework is included
報錯:- 在
MainApplication
中添加
new RealmReactPackage()
- 在
- 如果還是鏈接不上,我們檢查以下幾處代碼是否有自動添加
- settings.gradle 中是否有下麵代碼,不存在手動添加
include ':realm' project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android')
- 如果還不行,到app => build.gradle 中是否有下麵代碼,不存在手動添加
dependencies { compile project(':realm') // 是否存在,不存在手動添加(再舊版本有效,新版本不需要添加此項) compile fileTree(dir: "libs", include: ["*.jar"]) compile "com.android.support:appcompat-v7:23.0.1" compile "com.facebook.react:react-native:+" // From node_modules }
- 如果出現
- 接著,重新運行安卓:
react-native run-android
- 如果還是不行,可聯繫官方,或者將錯誤代碼發送給我,也許可以幫忙解決。
Realm 常用操作
- 作為資料庫,使用它無法就是
增刪改查
這老四樣,使用之前,還是老規矩,初始化表格:- name:表格名稱。
- primaryKey:主鍵,這個屬性的類型可以是 'int' 和 'string',並且如果設置主鍵之後,在更新和設置值的時候這個值必須保持唯一性,並且無法修改。
- properties:這個屬性內放置我們需要的欄位。
// 新建表模型
const PersonSchema = {
name: 'Person',
primaryKey:'id', // 官方沒給出自增長的辦法,而且一般不會用到主鍵,這也解決了重覆訪問的問題,而且實際開發中我們不需要主鍵的,讓服務端管就是了
properties: {
id:'int',
name: 'string',
tel_number: {type: 'string', default: '156xxxxxxxx'}, // 添加預設值的寫法
city: 'string' // 直接賦值的方式設置類型
}
};
- 初始化 Realm:
// 根據提供的表初始化 Realm,可同時往數組中放入多個表
let realm = new Realm({schema: [PersonSchema]});
- 增加數據:
// 增加
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:'波多野結衣', tel_number:'137xxxxxxxx', city:'xx省xx市xxxxxx'});
})
}
查詢數據
- 查詢所有數據:
// 查詢所有數據 let persons = realm.objects('Person'); console.log ('name:' + persons[0].name + 'city:' + persons[0].city)
- 根據條件查詢數據
// 查詢 inquireData() { let allData; // 獲取Person對象 let Persons = realm.objects('Person'); // 遍歷表中所有數據 for (let i = 0; i<Persons.length; i++) { let tempData = '第' + i + '個' + Persons[i].name + Persons[i].tel_number + Persons[i].city + '\n'; allData += tempData } this.setState({ data:allData }) } // 根據條件查詢 filteredData() { let allData; // 獲取Person對象 let Persons = realm.objects('Person'); // 設置篩選條件 let person = Persons.filtered('id == 1'); if (person) { // 遍歷表中所有數據 for (let i = 0; i<person.length; i++) { let tempData = '第' + (person[i].id + 1) + '個數據:' + person[i].name + person[i].tel_number + person[i].city + '\n'; allData += tempData } } this.setState({ data:'篩選到的數據:' + allData }) }
更新數據:
// 更新
upData() {
realm.write(() => {
// 方式一
realm.create('Person', {id: 0, name: '皮皮蝦,我們走', tel_number: '156xxxxxxxx', city: 'xx省xx市xxxxxx'}, true);
// // 方式二:如果表中沒有主鍵,那麼可以通過直接賦值更新對象
// // 獲取Person對象
// let Persons = realm.objects('Person');
// // 設置篩選條件
// let person = Persons.filtered('name == 蒼井空');
// // 更新數據
// person.name = '黃鱔門'
})
}
- 刪除數據:
// 刪除
removeData() {
realm.write(() => {
// 獲取Person對象
let Persons = realm.objects('Person');
// 刪除
realm.delete(Persons);
})
}