概述
在开发一款APP,对于数据的存储是在正常不过了,在此之前,【稀饭】这个应用还没有用到存储数据的地方,为了学习研究React Native的数据存储,打算给应用增加【我的收藏】和【观看历史】这两个功能。接下来,我们来看看如何实现。
关于React Native数据存储的解决方案
关于RN如何存储数据,有两种方案。
- AsyncStorage
- SQLite
第一种是官网提供的一种数据存储方案,它是一个简单的、异步的、持久化的Key-Value文件存储系统,它对于App来说是全局性的。如果你是个android的开发者,那么这就是类似于SharedPreferences,它适用于存储些系统设置、全局变量等简单的key-value数据,不适用于value过于庞大的数据,也不适用于一些包含数据结构等复杂数据;那么针对这种不足,我们需要借助SQLite,轻量的数据库,但RN并没有提供,如果你看完了之前的自定义模块 ,那么你也可以利用原生的SQLiteDatabase开发自己的一个数据库。显然,这种需求很普遍,网上肯定有很多现有的*,我们就可以直接拿来用了。这里推荐使用 react-native-sqlite-storage。
实现我的收藏功能
从上面的效果图来看,我们需要在详情页面增加一个收藏按钮,点击之后,空心图变实心图,然后在收藏列表里增加一条数据。要实现这个功能,需要在点击收藏之后,将需要的信息保存到数据库中,然后在列表页读取出来显示。这里利用react-native-sqlite-storage这个第三方库来实现数据的存储。
引入 react-native-sqlite-storage
安装
根据github上的文档说明(Android部分),首先我们在项目根目录下执行cmd命令:
npm install --save react-native-sqlite-storage
如果命令执行很久没有反应,建议换个npm镜像(淘宝镜像)
配置
修改android项目的settings.gradle
// file: android/settings.gradle
...
include ':react-native-sqlite-storage'
project(':react-native-sqlite-storage').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sqlite-storage/src/android')
修改app\build.gradle
// file: android/app/build.gradle
...
dependencies {
...
compile project(':react-native-sqlite-storage')
}
修改MainApplication.java,添加SQLitePluginPackage
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
protected boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new OrientationPackage(),
new VideoViewPackage(),
new SQLitePluginPackage()
);
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
}
到这里算是做好了前期的配置工作,下面结合实际需求讲述如何使用它。
接口封装
首先,我们需要封装一个SQLite模块,方便应用调用。
在项目根目录js下新建db文件夹,然后在js/db/下新建SQLite.js
import React from 'react';
import SQLiteStorage from 'react-native-sqlite-storage';
SQLiteStorage.DEBUG(true);
const SQLite = React.createClass({
render (){
return null;
},
});
module.exports = SQLite;
该模块类似于工具类,不需要渲染任何界面,所以render return null。
定义打开数据open和关闭数据库close的方法
open(){
db = SQLiteStorage.openDatabase(
database_name,
database_version,
database_displayname,
database_size,
()=>{
this._successCB('open');
},
(err)=>{
this._errorCB('open',err);
});
},
close(){
if(db){
this._successCB('close');
db.close();
}else {
console.log("SQLiteStorage not open");
}
db = null;
},
创建收藏表
字段 | 类型 | 说明 |
---|---|---|
id | INTEGER | 主键 |
name | VARCHAR | 电影名称 |
actor | VARCHAR | 主演 |
time | VARCHAR | 收藏时间 |
pic | VARCHAR | 封面 |
url | VARCHAR | 详情地址 |
title | VARCHAR | 标题 |
createTable(){
if (!db) {
open();
}
//创建收藏表
db.transaction((tx)=> {
tx.executeSql('CREATE TABLE IF NOT EXISTS ' + Collection_TABLE_NAME + '(' +
'id INTEGER PRIMARY KEY NOT NULL,' +
'name VARCHAR,' +
'actor VARCHAR,' +
'time VARCHAR,' +
'pic VARCHAR,' +
'url VARCHAR,' +
'title VARCHAR'
+ ');'
, [], ()=> {
this._successCB('executeSql');
}, (err)=> {
this._errorCB('executeSql', err);
});
}, (err)=> {
this._errorCB('transaction', err);
}, ()=> {
this._successCB('transaction');
})
}
以上完整的代码:
import React from 'react';
import SQLiteStorage from 'react-native-sqlite-storage';
SQLiteStorage.DEBUG(true);
var database_name = "xifan.db";
var database_version = "1.0";
var database_displayname = "MySQLite";
var database_size = -1;
var db;
const Collection_TABLE_NAME = "Collection";//收藏表
const SQLite = React.createClass({
render(){
return null;
},
componentWillUnmount(){
if(db){
this._successCB('close');
db.close();
}else {
console.log("SQLiteStorage not open");
}
},
open(){
db = SQLiteStorage.openDatabase(
database_name,
database_version,
database_displayname,
database_size,
()=>{
this._successCB('open');
},
(err)=>{
this._errorCB('open',err);
});
},
createTable(){
if (!db) {
open();
}
//创建收藏表
db.transaction((tx)=> {
tx.executeSql('CREATE TABLE IF NOT EXISTS ' + Collection_TABLE_NAME + '(' +
'id INTEGER PRIMARY KEY NOT NULL,' +
'name VARCHAR,' +
'actor VARCHAR,' +
'time VARCHAR,' +
'pic VARCHAR,' +
'url VARCHAR,' +
'title VARCHAR'
+ ');'
, [], ()=> {
this._successCB('executeSql');
}, (err)=> {
this._errorCB('executeSql', err);
});
}, (err)=> {
this._errorCB('transaction', err);
}, ()=> {
this._successCB('transaction');
})
},
close(){
if(db){
this._successCB('close');
db.close();
}else {
console.log("SQLiteStorage not open");
}
db = null;
},
_successCB(name){
console.log("SQLiteStorage "+name+" success");
},
_errorCB(name, err){
console.log("SQLiteStorage "+name+" error:"+err);
}
});
module.exports = SQLite;
然后在程序启动进入到首页时去创建表
MainScene.js
import SQLite from './db/SQLite';
var sqLite = new SQLite();
//省略其它代码
componentDidMount(){
sqLite.createTable();
}
componentWillUnmount(){
sqLite.close();
}
启动程序就可以看到成功执行了
以面向对象的思想来说,我们需要为收藏表的字段创建一个实体类对象Movie
在js/db下创建Movie.js
import React from 'react';
var id;
var name = "";
var actor = "";
var time = "";
var pic = "";
var url = "";
var title = "";
const Movie = React.createClass({
render(){
return null;
}
,
setId(id){
this.id = id;
},
getId(){
return this.id;
},
setName(name){
this.name = name;
},
getName(){
return this.name;
},
setActor(actor){
this.actor = actor;
},
getActor(){
return this.actor;
},
setTime(time){
this.time = time;
},
getTime(){
return this.time;
},
setPic(pic){
this.pic = pic;
},
getPic(){
return this.pic;
},
setUrl(url){
this.url = url;
},
getUrl(){
return this.url;
},
setTitle(title){
this.title = title;
},
getTitle(){
return this.title;
}
});
module.exports = Movie;
接着,我们为收藏表增加增删查方法
saveCollection(movie){//保存收藏记录
return new Promise((resolve, reject)=>{
if(db){
db.executeSql(
'INSERT INTO '+Collection_TABLE_NAME+' (name,actor,time,pic,url,title) VALUES(?,?,?,?,?,?)',
[movie.getName(),movie.getActor(),movie.getTime(),movie.getPic(),movie.getUrl(),movie.getTitle()],
()=>{
this._successCB('saveCollection');
resolve();
},
(err)=>{
this._errorCB('saveCollection',err);
reject();
})
}else {
reject('db not open');
}
});
}
findCollectionByName(name){//通过影片名称获取对应收藏记录
return new Promise((resolve, reject)=>{
if(db){
db.executeSql('SELECT * FROM '+Collection_TABLE_NAME +' WHERE name=? LIMIT 1',[name],
(results)=>{
console.log(results);
if(results.rows.length > 0){
resolve(results.rows.item(0));
}else {
reject('not find item');
}
this._successCB('findCollectionByName')
},(err)=>{
reject(err);
this._errorCB('findCollectionByName',err)
});
}else {
reject('db not open');
}
});
}
deleteCollectionByName(name){//通过影片名称删除对应收藏记录
return new Promise((resolve, reject)=>{
if(db){
db.executeSql('DELETE FROM '+Collection_TABLE_NAME +' WHERE name=?',[name],
()=>{
resolve();
this._successCB('deleteCollectionByName');
},(err)=>{
reject(err);
this._errorCB('deleteCollectionByName',err);
});
}else {
reject('db not open');
}
});
}
listCollection(pageSize,index){//获取收藏记录列表
return new Promise((resolve, reject)=>{
if(db){
db.executeSql('SELECT * FROM '+Collection_TABLE_NAME +' LIMIT '+pageSize+' OFFSET '+((index-1)*pageSize),[],
(results)=>{
var len = results.rows.length;
var datas = [];
for(let i=0;i<len;i++){
datas.push(results.rows.item(i));
}
resolve(datas);
this._successCB('listCollection');
},(err)=>{
reject(err);
this._errorCB('listCollection',err);
});
}else {
reject('db not open');
}
});
}
这几个方法都使用到了Promise,这使得对象调用时可以使用链式的方法,更加方便。
接口调用
定义完接口,我们就可以按需求来实现了。在详情页DramaDetailScene.js添加一个收藏按钮,如效果图,这里不在阐述UI的实现,直接来看如何保存数据。
//收藏
_onCollectionPress(movie){
console.log(movie);
/*{ name: '信义',
title: '全集中字',
actor: '金喜善,李敏镐,刘德焕,朴世英,李必立,沈恩京,成勋,李民浩',
pic: 'http://img.y3600.com/d/file/p/2016/10/26/40d39df617fc663a21f1e433e67742de.jpg',
url: '/hanju/2016/958.html' }*/
var isCollection = !this.state.isCollection;
if(isCollection){//保存
var coll = new Movie();
coll.setName(movie.name);
coll.setActor(movie.actor);
coll.setPic(movie.pic);
coll.setUrl(movie.url);
coll.setTitle(movie.title);
var date = new Date();
var time=date.getFullYear()+'-'+(date.getMonth()+1)+'-'+date.getDate()+' ';
var hours = date.getHours();
if(hours < 9){
time = time+'0'+hours+':';
}else {
time = time+hours+':';
}
var minutes = date.getMinutes();
if(minutes < 9){
time = time+'0'+minutes+':';
}else {
time = time+minutes+':';
}
var sec = date.getSeconds();
if(sec < 9){
time = time+'0'+sec;
}else {
time = time+sec;
}
coll.setTime(time);
sqlite.saveCollection(coll).then(()=>{
this.setState({
isCollection:isCollection,
});
}).catch((e)=>{}).done();
}else {//删除
sqlite.deleteCollectionByName(this.props.data.name).then(()=>{
this.setState({
isCollection:isCollection,
})
}).catch((e)=>{}).done();
}
}
我们通过this.state.isCollection来保存收藏状态。当未收藏时执行保存,已收藏时执行删除。
再者,当我们一进入详情页时,需要知道是否已经收藏。
componentDidMount(){
sqlite.findCollectionByName(this.props.data.name).then((result)=>{
if(result){
this.setState({
isCollection:true,
});
}
}).catch((e)=>{}).done();
this._fetchData(this.props.data.url);
}
最后就是在我的收藏列表检索出所有的收藏记录并展示
_queryData(){
sqlite.listCollection(10,index).then((results)=>{
datas = datas.concat(results);
this.setState({
movies:this.state.movies.cloneWithRows(datas),
isRefreshing:false
});
}).catch((err)=>{
}).done();
}
【观看历史】也是差不多这个流程,具体的实现不在这里贴代码了,更多请查看我的github