在 app 开发过程中数据存储是必不可少的, RN 中数据存储一般都用 AsyncStorage. 但是对于大批量的数据持久化存储, 最好还是用数据库来存. RN 中并没有提供直接的数据库存储 API, 需要我们自己根据 iOS 和 Android 进行封装调用.
Github 上有个库提供了对原生 sqlite 数据库的操作封装
react-native-sqlite-storage
https://github.com/andpor/react-native-sqlite-storage , 看过之后我觉得还是自己分别在 Android 和 iOS 原生端来实现数据库存储更好, 原生端数据库 API 非常简单, 尤其是 Android,iOS 也可以借助第三方 FMDB 来实现. 不过对于不熟悉 Android 或者 iOS 的人来说, 直接使用这个库是最好的选择.
在我之前的文章RN 与原生交互 (二)-- 数据传递中已经说明了 RN 如何调用原生端方法获取数据, 这里 RN 调用原生 sqlite 数据库原理也一样, 都是在原生端写好所有的封装操作, 以 Native Module 的形式供 RN 端调用.
我写了个简单的 Demo, 实现了数据库的创建和基本的增删改查操作, 效果如下:
demo.gif
下面来说说具体实现方式.
Android 端
Android 端 sqlite 数据库的使用非常简单, 官方提供了 SQLiteDatabase 和 SQLiteOpenHelper 等相关类来操作数据库, API 非常简单. Android 端具体实现步骤如下:
先创建一个 DBHelper 类继承 SQLiteOpenHelper, 重写 onCreate 和 onUpgrade 方法, 并创建该类的构造函数:
- public class DBHelper extends SQLiteOpenHelper {
- private static final String DB_NAME = "StudentDB.db"; // 数据库名称
- private static final int version = 1; // 数据库版本
- 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);
- }
- }
复制代码
创建 DBManager 类, 将所有数据库的增删改查操作放到这里面来.
数据的查询使用 Cursor, 插入数据使用 android 的 ContentValues, 非常简单, 这点比 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);
- }
- /**
- * 是否存在此条数据
- * @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;
- }
- /**
- * 保存数据
- */
- 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();
- }
- }
- }
- }
复制代码
创建 module 类继承
ReactContextBaseJavaModule
, 将 DBManager 中的增删改查方法导出供 RN 端直接调用. 部分核心代码:
- 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);
- }
- }
- }
复制代码
创建 package 类继承 ReactPackage, 实现这个接口的方法, 将上面创建的 module 类在
createNativeModules
方法中实例化.
- 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 端就可以调用 Android 的数据库存储数据了.
iOS 端
iOS 端数据库的存储一般都不使用原生 api, 因为它原生 api 不那么友好. 我们一般使用 FMDB 来实现数据库的存储操作, 用 CoreData 也可以, 原理都一样, 这里以 FMDB 为例.
创建 Podfile, 使用 CocoaPods 安装 FMDB.
在项目的 Build Phases --> Link Binary With Libraries 中添加 libsqlite3.tbd 库.
创建 DBHelper 类
不同于 Android 可以直接继承 SQLiteOpenHelper 直接重写方法就 OK 了, iOS 数据库的存储操作还是需要我们自己完成.
创建 DBHelper 的单例, 指定数据库文件, 创建数据库和表, 核心代码如下:
- + (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];
- }
- }
复制代码
创建 module 类, 这里 module 类名字应该与 Android 端一致, 方便 RN 端调用的时候统一. 这里名字起为 DBManagerModule,iOS 端 module 类只需要实现 RCTBridgeModule 协议就可以了, 这一步比 Android 要更简单. 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 端就可以直接调用 module 类中的方法操作 iOS 数据库了.
RN 端的用法
比如查询所有数据:
- DBManagerModule.getAllStudent((result) => {
- let students = [];
- if (result != null) {
- students = result;
- this.setState({
- studentList: students
- })
- }
- });
复制代码
总结
RN 端量小的数据可以使用 AsyncStorage, 大数据量需要存储还是要用数据库.
经过实践, 我觉得还是直接在原生端操作数据库更好, api 简单也方便维护. 第三方库
- react-native-sqlite-storage
- https://github.com/andpor/react-native-sqlite-storage
也是在原生端的基础上做的封装, 好处是方便 RN 端调用, 不熟悉原生的可以直接按照配置说明来使用, 缺点也很明显, 配置繁琐, 使用过程中出了问题也不容易解决.
来源: https://juejin.im/entry/5b40cf23e51d45198855c2ee