React Native 学习之AsyncStorage

在APP开发中,不管是Android还是IOS,都会有一些配置信息需要保存到设备中。保存数据大部分都是用数据库,但是若数据量不是很多的话,去用数据来保存的话就有点杀鸡焉用牛刀了。对于用户轻量级的数据持久化,在Android平台上是用的SharedPreference,而在IOS平台上用的是NSUserDefaults,它们都是以 "key-value(键值对)"形式保存数据的。

对于RN来说,它也提供了一个存储轻量级数据的结构,也就是我们今天要学习的AsyncStorage。它是一个简单的、具有异步特性的的键值对的存储系统。通过一个简单的购物车Demo来学习AsyncStorage的用法,效果如下:

React Native 学习之AsyncStorage_第1张图片
AsyncStorage_IOS.gif

API学习

对于数据的存储,一般会涉及到四个方面,增删改查。那我们就来看看RN给我们提供哪些API来进行这四个操作了。

// 根据键来获取值,获取到的结果会在回调函数中
getItem(key : string, callback:(error,result)) 

//设置键值对
setItem(key : string, value : string, callback:(error))

//根据键移除一项
removeItem(key : string, callback:(error))

//合并现有值和输入值(其实就是更新某个key对应的旧value值)
mergeItem(key : string, value : string, callback:(error))

//清除所有项目
clear(callback:(error))

//获取所有的键
getAllKeys(callback:(error))

//获取多项,其中keys是字符串数组
multiGet(keys, callback:(error,result)) 

//设置多项,其中keyValuePairs是字符串的二维数组
multiSet(keyValuePairs, callback:(errors))

//删除多项,其中keys是字符串数组
multiRemove(keys, callback:(error)) 

//多个键值对合并,其中keyValuePairs是字符串的二维数组
multiMerge(keyValuePairs, callback:(errors))

我们可以看到,每个方法都有一个回调方法,而回调方法的第一个参数都是错误对象。如果发生错误,该对象就会展示错误信息,否则为null。所有的方法执行后,都会返回一个Promise对象。了解更多Promise信息 所以我们在使用AsyncStorage时,自己可以做一层封装,通过返回Promise对象来进行其他的一些异步操作等

Demo主要实现

  • 界面UI渲染

    水果列表界面肯定有很多数据,所以我们这里肯定是用ListView来显示

      render() {
          let count = this.state.count;
          let str = '';
          if (count) {
              str = ', 共' + count + '件商品';
          }
          return (
              
                  
                      水果列表
                  
                  
    
                  this._onPress()}
                  >
                      去结算{str}
                  
              
          );
      }
    
  • 数据查询

    用户每次打开APP时,我们都需要给用户显示购物车是否有商品,所以要去AsyncStorage中查询商品数量。而这一步是属于耗时操作,所有我们将它放在componentDidMount()方法里面。在所有的生命周期方法,它一般用来处理一些复杂的逻辑以及耗时任务。

      _getAsyncStorageStatus() {
          AsyncStorage.getAllKeys((err, keys)=> {
    
              if (err) {
                  //TODO 存储数据出错,给用户提示错误信息
              }
    
              this.setState({
                  count: keys.length
              });
          });
      }
    
  • 添加商品到购物车

    在本Demo中,我们在点击商品时就会把它添加到购物车中,也就是用AsyncStorage将数据保存起来

      _addGoodsToShoppingCar(rowData) {
    
          console.log(rowData);
          let count = this.state.count;
          count++;
          this.setState({
              count: count
          });
    
          //AsyncStorage存储
          AsyncStorage.setItem('SP-' + this._getId() + '-SP', JSON.stringify(rowData), (err)=> {
    
              if (err) {
                  //TODO 存储出错
              }
          });
      }
    
      /**
       * 生成随机ID:GUID
       * GUID生成的代码来源于Stoyan Stefanov
       * @private
       */
      _getId() {
          return 'xxxxxxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c)=> {
              let r = Math.random() * 16 | 0;
              let v = c == 'x' ? r : (r & 0x3 | 0x8);
              return v.toString(16);
          }).toUpperCase();
      }
    

    这里之所以使用SP-为前缀、-SP为后缀,采用GUID为存储的键名的一部分,是为了区分其他数据,并且还有两个好处:

    • 可以区分用户数据,例如userName等信息;
    • 可以防止key值重复,保证同名商品都能被添加进购特车
  • 清除购物车

    在购物车界面,我们有一个button是用来清除购物车的,也就是AsyncStorage里面的数据

      _clearStorage() {
          AsyncStorage.clear((err)=> {
    
              //TODO err处理
    
              this.setState({
                  data: [],
                  price: 0
              }, ()=> {
                  //发送消息
                  DeviceEventEmitter.emit('clearStorage', {isClearSuccess: true});
              });
    
              alert('购物车已经清空');
    
          });
      }
    

Demo遇到的问题

好了,购物车Demo基本上就完了,但是在运行时发现一个小bug,如下图:

React Native 学习之AsyncStorage_第2张图片
bug.gif

我们在购物车界面清除掉了购物车后,再回到水果列表界面,但是我们的 "去结算"button仍然显示还有4件商品,很显然是我们的AsyncStorage没有更新,怎样去更新数据是非常简单的,但问题是在哪里去更新?

  • 方式一:

    作为有经验的开发人员,我们马上会想到RN的生命周期。是的,不错,确实是这样。当我们启动APP时,会执行的生命周期有:constructor,componentWillMount,render,componentDidMount,componentDidUpdate,除了componentDidUpdate会多次执行外,在一个route未卸载时,其它方法都只会执行一次,所以,从水果列表界面跳转到购物车界面,再按返回键回到水果列表界面时,除了componentDidUpdate外,其它不会执行。因为我们跳转是用push,从水果列表界面push到购物车界面时,push方法并不会把水果列表界面卸载掉,所以当pop掉购物车界面时,水果列表界面的生命周期不会执行。

    那难道就没有其他方法了?当然不。既然push不能把一个route从routeStack里卸载掉的会,我们可以找一个可以卸载掉的方法嘛,那就是replace,这样的话,回到水果列表界面时就可以重新走生命周期了,也就是可以重新获取AsyncStorage里面的信息了。那按返回键时就不能直接pop掉了,可以用push或者replace方法。但这种做法有一点不好的是界面需要重新渲染,个人认为体验效果不是很好。

  • 方式二

    我想到的第二种方式是监听购物车的清空。在水果列表界面注册一个监听器:

      componentWillMount() {
          
          DeviceEventEmitter.addListener('clearStorage', (result)=> {
              if (result.isClearSuccess) {
                  this._getAsyncStorageStatus();
              }
          });
      }
    

    第一个参数是接收事件名,第二个参数是接收事件结果的回调。listner既然注册好了,那就在点击清空购物车button时发送一个息:

      DeviceEventEmitter.emit('clearStorage', {isClearSuccess: true});
    

我们来看看效果:

React Native 学习之AsyncStorage_第3张图片
bug_resolve.gif

从效果图来看,确实可以实现,但是在Android平台上会出现一个warning,IOS没有。如下图:

React Native 学习之AsyncStorage_第4张图片
warning_android.png

警告说setState只能在一个route被mounting或者mounted时才能被调用。很显然,我们在点击清空按钮时,水果列表界面压根就没有被mounting或者mounted,但我们却setState了。

这种报警告的问题,忽略它的话也没有什么问题,但我们公司在Code Review时是不被允许的,虽然看起来没有问题,但仍然存在一些潜在的风险。

好了,AsyncStorage的学习就到这里了。若各位对上面提的那个小bug有完美的解决方案的话,烦请告诉我啊。

完整代码下载

你可能感兴趣的:(React Native 学习之AsyncStorage)