美文网首页React-NativeH5+JSReact Native实践
React Native 学习之AsyncStorage

React Native 学习之AsyncStorage

作者: BlainPeng | 来源:发表于2016-11-13 13:43 被阅读304次

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

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

    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 (
                <View style={{backgroundColor: 'white'}}>
                    <View style={styles.headViewContainer}>
                        <Text style={styles.headTextStyle}>水果列表</Text>
                    </View>
                    <ListView
                        dataSource={this.state.dataSource}
                        renderRow={this._renderRow.bind(this)}
                        contentContainerStyle={styles.listViewContentStyle}
                    />
      
                    <TouchableOpacity style={styles.btnStyle}
                                      activeOpacity={0.5}
                                      onPress={()=>this._onPress()}
                    >
                        <Text style={styles.btnTextStyle}>去结算{str}</Text>
                    </TouchableOpacity>
                </View>
            );
        }
      
    • 数据查询

      用户每次打开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,如下图:

    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});
      

    我们来看看效果:

    bug_resolve.gif

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

    warning_android.png

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

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

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

    完整代码下载

    相关文章

      网友评论

        本文标题:React Native 学习之AsyncStorage

        本文链接:https://www.haomeiwen.com/subject/xwrcpttx.html