前言

数据持久化一直都是软件开发中重要的一个环节,几乎所有的应用都具备这一项功能;那什么是数据持久化呢?—— 说白了就是数据的本地化存储,将数据存储到本地,在需要的时候进行调用。

本文介绍两种在 React-Native 中比较常用的存储方式:AsyncStorageRealm

  • AsyncStorage:官方使用的存储方式,类似于 iOS 中的 NSUserDefault ,区别在于,AsyncStorage 只能存储字符串键值对,而 NSUserDefault 可以存储字符串和number。
  • Realm:新兴的移动端数据存储方式,在它未出现之前,移动端一直都是使用 sqlist 进行数据存储,在性能上,各有优势,但是操作上,Realm 有着明显优势,更方便使用。

接下来我们就来看看怎么使用它们。

AsyncStorage

AsyncStorage方法官方文档写得很详细,这边就不对赘述了!

示例代码如下:

// 增加
createData() {
   AsyncStorage.setItem('name', JSON.stringify('Hello RN!'), (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('Hello Vue'), (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
 
  • 1

接着,创建一个 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;
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

到这里,我们需要注意的就是要在哪里初始化这个文件,其实一个思路就是 —— 在哪个地方,我们只需要引用一次文件,就可以在其他文件中使用(比如:我们程序默认的进口就是 index.ios/android.js 文件,那么只要在他们中引用一次文件即可,这样就不需要去注意什么调用顺序,因为 index.ios/android.js 文件肯定是最先调用的,它们才是真正的王)。

然而,为了方便我们使用同一套代码,我们会创建一个 Main 文件作为程序入口的 中转总站 来管理其他的文件,然后外界只要调用这个 Main 文件,就可以展示里面的所有东西。所以,将引用放到 Main 文件中是最好的选择。

  // 在 main 文件中添加
    import storage from '封装的文件位置';
 
  • 1
  • 2

到这里,就完成了最基础的配置,只需要在需要用到的地方直接使用就可以了,首先我们在新建一个文件,然后从Main文件跳转到这个文件中。

接着,使用一下这个框架:

// 增加
createData() {
  // 使用key保存数据
  storage.save({
      key:'storageTest',    // 注意:请不要在key中使用_下划线符号!
      rawData: {
          name:'Vue',
          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:'Angular',
          city:'xx省xxx市'
      },

      // 设为null,则不过期,这里会覆盖初始化的时效
      expires: 1000 * 3600
  });
}

// 删除
removeData() {
  // 删除单个数据
  storage.remove({
      key: 'storageTest'
  });

  // storage.remove({
  //     key: 'react-native-storage-test',
  //     name:'Vue'
  // });

//         // !! 清空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');
}
 

Realm

首先,将 Realm 引入工程中。

npm install --save realm
 
  • 1

接着,添加 Realm 与 工程的链接

// React-Native >= 0.31.0

  react-native link realm
// React-Native < 0.31.0

  rnpm link realm
 

Realm 常用操作

作为数据库,使用它无非就是 CRUD 操作,使用之前,初始化表格:

  • 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 == Vue');
     // // 更新数据
     // person.name = '黄鳝门'

 })
}
 

删除数据

// 删除
removeData() {
    realm.write(() => {
        // 获取Person对象
        let Persons = realm.objects('Person');
        // 删除
        realm.delete(Persons);
    })
}
 

web前端开发工程师面试题(react)