在app開(kāi)發(fā)過(guò)程中數(shù)據(jù)存儲(chǔ)是必不可少的着撩,RN中數(shù)據(jù)存儲(chǔ)一般都用AsyncStorage。但是對(duì)于大批量的數(shù)據(jù)持久化存儲(chǔ)刁笙,最好還是用數(shù)據(jù)庫(kù)來(lái)存膨蛮。RN中并沒(méi)有提供直接的數(shù)據(jù)庫(kù)存儲(chǔ)API,需要我們自己根據(jù)iOS和Android進(jìn)行封裝調(diào)用竖慧。
Github上有個(gè)庫(kù)提供了對(duì)原生sqlite數(shù)據(jù)庫(kù)的操作封裝react-native-sqlite-storage
嫌套,看過(guò)之后我覺(jué)得還是自己分別在Android和iOS原生端來(lái)實(shí)現(xiàn)數(shù)據(jù)庫(kù)存儲(chǔ)更好,原生端數(shù)據(jù)庫(kù)API非常簡(jiǎn)單圾旨,尤其是Android踱讨,iOS也可以借助第三方FMDB來(lái)實(shí)現(xiàn)。不過(guò)對(duì)于不熟悉Android或者iOS的人來(lái)說(shuō)砍的,直接使用這個(gè)庫(kù)是最好的選擇痹筛。
在我之前的文章《RN與原生交互(二)——數(shù)據(jù)傳遞》中已經(jīng)說(shuō)明了RN如何調(diào)用原生端方法獲取數(shù)據(jù),這里RN調(diào)用原生sqlite數(shù)據(jù)庫(kù)原理也一樣,都是在原生端寫(xiě)好所有的封裝操作帚稠,以Native Module的形式供RN端調(diào)用谣旁。
我寫(xiě)了個(gè)簡(jiǎn)單的Demo,實(shí)現(xiàn)了數(shù)據(jù)庫(kù)的創(chuàng)建和基本的增刪改查操作滋早,效果如下:
下面來(lái)說(shuō)說(shuō)具體實(shí)現(xiàn)方式榄审。
Android端
Android端sqlite數(shù)據(jù)庫(kù)的使用非常簡(jiǎn)單,官方提供了SQLiteDatabase和SQLiteOpenHelper等相關(guān)類來(lái)操作數(shù)據(jù)庫(kù)杆麸,API非常簡(jiǎn)單搁进。Android端具體實(shí)現(xiàn)步驟如下:
- 先創(chuàng)建一個(gè)DBHelper類繼承SQLiteOpenHelper,重寫(xiě)onCreate和onUpgrade方法昔头,并創(chuàng)建該類的構(gòu)造函數(shù):
public class DBHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "StudentDB.db"; //數(shù)據(jù)庫(kù)名稱
private static final int version = 1; //數(shù)據(jù)庫(kù)版本
public static final String STUDENT_TABLE = "Student";
public DBHelper(Context context) {
super(context, DB_NAME, null, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
String sql = "create table if not exists " + STUDENT_TABLE +
" (studentName text primary key, schoolName text, className text)";
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
String sql = "DROP TABLE IF EXISTS " + STUDENT_TABLE;
db.execSQL(sql);
onCreate(db);
}
}
- 創(chuàng)建DBManager類饼问,將所有數(shù)據(jù)庫(kù)的增刪改查操作放到這里面來(lái)。
數(shù)據(jù)的查詢使用Cursor揭斧,插入數(shù)據(jù)使用android的ContentValues莱革,非常簡(jiǎn)單,這點(diǎn)比iOS原生的API好用一百倍讹开。部分核心代碼如下:
public class DBManager {
private static final String TAG = "StudentDB";
private DBHelper dbHelper;
private final String[] STUDENT_COLUMNS = new String[] {
"studentName",
"schoolName",
"className",
};
public DBManager(Context context) {
this.dbHelper = new DBHelper(context);
}
/**
* 是否存在此條數(shù)據(jù)
* @return bool
*/
public boolean isStudentExists(String studentName) {
boolean isExists = false;
SQLiteDatabase db = null;
Cursor cursor = null;
try {
db = dbHelper.getReadableDatabase();
String sql = "select * from Student where studentName = ?";
cursor = db.rawQuery(sql, new String[]{studentName});
if (cursor.getCount() > 0) {
isExists = true;
}
} catch (Exception e) {
Log.e(TAG, "isStudentExists query error", e);
} finally {
if (cursor != null) {
cursor.close();
}
if (db != null) {
db.close();
}
}
return isExists;
}
/**
* 保存數(shù)據(jù)
*/
public void saveStudent(String studentName, String schoolName, String className) {
SQLiteDatabase db = null;
try {
db = dbHelper.getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put("studentName", studentName);
cv.put("schoolName", schoolName);
cv.put("className", className);
db.insert(DBHelper.STUDENT_TABLE, null, cv);
} catch (Exception e) {
Log.e(TAG, "saveStudent error", e);
} finally {
if (db != null) {
db.close();
}
}
}
}
- 創(chuàng)建module類繼承
ReactContextBaseJavaModule
驮吱,將DBManager中的增刪改查方法導(dǎo)出供RN端直接調(diào)用。部分核心代碼:
public class DBManagerModule extends ReactContextBaseJavaModule {
private ReactContext mReactContext;
public DBManagerModule(ReactApplicationContext reactContext) {
super(reactContext);
mReactContext = reactContext;
}
@Override
public String getName() {
return "DBManagerModule";
}
@ReactMethod
public void saveStudent(String studentName, String schoolName, String className) {
DBManager dbManager = new DBManager(mReactContext);
if (!dbManager.isStudentExists(studentName)) {
dbManager.saveStudent(studentName, schoolName, className);
}
}
}
- 創(chuàng)建package類繼承
ReactPackage
萧吠,實(shí)現(xiàn)這個(gè)接口的方法左冬,將上面創(chuàng)建的module類在createNativeModules
方法中實(shí)例化。
public class DBManagerPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> nativeModules = new ArrayList<>();
nativeModules.add(new DBManagerModule(reactContext));
return nativeModules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
這樣在RN端就可以調(diào)用Android的數(shù)據(jù)庫(kù)存儲(chǔ)數(shù)據(jù)了纸型。
iOS端
iOS端數(shù)據(jù)庫(kù)的存儲(chǔ)一般都不使用原生api拇砰,因?yàn)樗鷄pi不那么友好。我們一般使用FMDB來(lái)實(shí)現(xiàn)數(shù)據(jù)庫(kù)的存儲(chǔ)操作狰腌,用CoreData也可以除破,原理都一樣,這里以FMDB為例琼腔。
- 創(chuàng)建Podfile瑰枫,使用CocoaPods安裝FMDB。
- 在項(xiàng)目的Build Phases ——> Link Binary With Libraries中添加libsqlite3.tbd庫(kù)丹莲。
- 創(chuàng)建DBHelper類
不同于Android可以直接繼承SQLiteOpenHelper直接重寫(xiě)方法就OK了光坝,iOS數(shù)據(jù)庫(kù)的存儲(chǔ)操作還是需要我們自己完成。
創(chuàng)建DBHelper的單例甥材,指定數(shù)據(jù)庫(kù)文件盯另,創(chuàng)建數(shù)據(jù)庫(kù)和表,核心代碼如下:
+ (DBHelper *)sharedDBHelper {
static DBHelper *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
- (instancetype)init {
self = [super init];
if (self) {
_db = [[FMDatabase alloc] initWithPath:[self getDBFilePath]];
[self createTables];
}
return self;
}
- (NSString *)getDBFilePath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *storePath = [documentsDirectory stringByAppendingPathComponent:@"StudentDB.db"];
return storePath;
}
- (void)createTables {
if ([_db open]) {
NSMutableString *sql = [NSMutableString string];
[sql appendString:@"create table if not exists Student ("];
[sql appendString:@"studentName text primary key, "];
[sql appendString:@"schoolName text, "];
[sql appendString:@"className text);"];
BOOL result = [_db executeUpdate:sql];
if (result) {
NSLog(@"create table Student successfully.");
}
[_db close];
}
}
- 創(chuàng)建module類洲赵,這里module類名字應(yīng)該與Android端一致鸳惯,方便RN端調(diào)用的時(shí)候統(tǒng)一商蕴。這里名字起為DBManagerModule,iOS端module類只需要實(shí)現(xiàn)RCTBridgeModule協(xié)議就可以了芝发,這一步比Android要更簡(jiǎn)單绪商。DBManagerModule核心代碼:
@implementation DBManagerModule
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(saveStudent:(NSDictionary *)dict) {
[[DBHelper sharedDBHelper] saveStudent:dict];
}
RCT_EXPORT_METHOD(deleteStudent:(NSString *)studentName) {
[[DBHelper sharedDBHelper] deleteStudentByName:studentName];
}
RCT_EXPORT_METHOD(getAllStudent:(RCTResponseSenderBlock)callback) {
NSArray *students = [[DBHelper sharedDBHelper] getAllStudent];
callback(@[students]);
}
RCT_EXPORT_METHOD(deleteAllStudent) {
[[DBHelper sharedDBHelper] deleteAllStudent];
}
@end
到這里RN端就可以直接調(diào)用module類中的方法操作iOS數(shù)據(jù)庫(kù)了。
RN端的用法
比如查詢所有數(shù)據(jù):
DBManagerModule.getAllStudent((result) => {
let students = [];
if (result != null) {
students = result;
this.setState({
studentList: students
})
}
});
總結(jié)
- RN端量小的數(shù)據(jù)可以使用AsyncStorage辅鲸,大數(shù)據(jù)量需要存儲(chǔ)還是要用數(shù)據(jù)庫(kù)格郁。
- 經(jīng)過(guò)實(shí)踐,我覺(jué)得還是直接在原生端操作數(shù)據(jù)庫(kù)更好瓢湃,api簡(jiǎn)單也方便維護(hù)。第三方庫(kù)
react-native-sqlite-storage
也是在原生端的基礎(chǔ)上做的封裝赫蛇,好處是方便RN端調(diào)用绵患,不熟悉原生的可以直接按照配置說(shuō)明來(lái)使用,缺點(diǎn)也很明顯悟耘,配置繁瑣落蝙,使用過(guò)程中出了問(wèn)題也不容易解決。
PS:
推薦一下demo中用到的RN第三方庫(kù):
-
react-native-navigation
基于原生的導(dǎo)航庫(kù) -
teaset
非常好的React Native UI組件庫(kù) -
react-native-vector-icons
iconfont組件庫(kù) -
react-native-swipe-list-view
仿iOS列表側(cè)滑顯示更多操作的React Native列表組件暂幼。雖然有bug筏勒,但不影響使用。