美文网首页
RN:网络编程、数据持久化与离线缓存的实现

RN:网络编程、数据持久化与离线缓存的实现

作者: 意一ineyee | 来源:发表于2019-08-28 11:10 被阅读0次

目录

一. 网络编程
二. 数据持久化
 1. AsyncStorage是什么
 2. 怎么使用AsyncStorage
三. 离线缓存的实现
 1. 为什么要做离线缓存
 2. 几种离线缓存策略
 3. “优先读取缓存型”策略的实现

一. 网络编程


RN的网络编程除了普通的网络请求,还支持WebSocket,这种协议可以在单个TCP连接上提供全双工的通信信道。我们可以在用到时,去做详细地学习。本篇只学习RN网络编程的普通网络请求。

  • fetch方法发起请求

RN里,我们用fetch方法从服务端请求数据或者给服务端上传数据,它的API很简单。

Promise fetch(url, configs);

它可以接收两个参数:第一个参数必填,字符串,是请求的url。第二个参数可选,JS对象,是请求的一些配置,如你可以指定请求的方式、请求头、请求体(即你要上传给服务端的数据)。它天然是一个异步操作,所以执行完后返回一个Promise。

举个例子。

Promise promise = fetch('https://mywebsite.com/endpoint/', {
    // 请求的方式
    method: 'POST',
    // 请求头
    headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
    },
    // 请求体
    body: JSON.stringify({
        firstParam: 'yourValue',
        secondParam: 'yourOtherValue',
    }),
});

注意:

我们上传给服务端数据的格式取决于headers中的Content-TypeContent-Type有很多种,因此对应body的格式也有区别。到底应该采用什么样的Content-Type取决于服务器端,所以请和服务器端的开发人员沟通确定清楚。

RN默认的请求方式为GET,用GET请求时,我们可以不用写请求方式和请求体,请求头按需配置,因此一个最简单的GET请求如下。

Promise promise = fetch('https://mywebsite.com/mydata.json?firstParam=yourValue&secondParam=yourOtherValue');
  • 处理服务端返回的数据

上面我们说到fetch方法是个天然的异步操作,它执行后会返回一个Promise,我们正是用该Promise对象来处理服务端返回的数据。

举个例子。

fetch('https://facebook.github.io/react-native/movies.json')
    .then(response => {
        if (response.ok) {
            // 请求到的response其实是一个Response对象,它是一个很原始的数据格式,我们不能直接使用,先获取它的JSON字符串文本格式
            return response.text();
        }
        throw new Error('网络请求失败!');
    })
    .then(responseText => {

        // 然后把JSON字符串序列化为JS对象
        const responseJSObj = JSON.parse(responseText);

        console.log(responseJSObj.movies);
    })
    .catch(error => {
        console.error(error);
    });

上面代码用Promise对象的then方法处理服务端响应成功时的数据,用catch方法处理服务端响应失败时错误信息。但是要注意只有请求被阻止或者网络故障时才属于触发catch这种情况,即便是当服务端返回的响应是404或500,也不会走catch方法,而是走then方法,只不过此时responseok属性值为false,所以我们需要在then方法里处理服务端响应成功数据时做一下判断。

  • 我们可以简单封装一个网络请求的工具类。
// ProjectRequest.js

export default class ProjectRequest {
    static get(url) {
        return new Promise((resolve, reject) => {
            fetch(url)
                .then(response => {
                    if (response.ok) {
                        return response.text();
                    } else {
                        throw new Error('网络请求失败!');
                    }
                })
                .then(responseText => {
                    const responseJSObj = JSON.parse(responseText);
                    resolve(responseJSObj);
                })
                .catch((error) => {
                    reject(error);
                })
        })
    }

    /**
     * RN提供的fetch方法,是异步的,它本身就会返回一个Promise对象。但因为这里我们对它进行了封装,所以外面又包了一层Promise,来给fetch这个异步任务提供回调,这样外界才能拿到fetch的结果。
     *
     * @param url
     * @param params
     * @returns {Promise<any> | Promise}
     */
    static post(url, params) {
        return new Promise((resolve, reject) => {
            fetch(url, {
                method: 'POST',
                headers: {
                    Accept: 'application/json',
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(params)
            })
                .then(response => {
                    if (response.ok) {
                        // 请求到的response其实是一个Response对象,它是一个很原始的数据格式,我们不能直接使用,先获取它的JSON字符串文本格式
                        return response.text();
                    } else {
                        throw new Error('网络请求失败!');
                    }
                })
                .then(responseText => {

                    // 然后把JSON字符串序列化为JS对象
                    const responseJSObj = JSON.parse(responseText);

                    // 把请求成功的数据传递出去
                    resolve(responseJSObj);
                })
                .catch((error) => {
                    // 把请求失败的信息传递出去
                    reject(error);
                })
        })
    }
}

二. 数据持久化


1. AsyncStorage是什么

AsyncStorage是RN里的一种数据持久化方案,它是一个简单的、异步的、Key-Value存储系统,它对于App来说是全局的,非常类似于我们iOS里的NSUserDefault

下面介绍一下它的常用API。

先声明两点:

  • AsyncStorage是一个天然的异步操作,所以下面它所有的API执行后,都会返回一个Promise对象。

  • 正如JS对象的key必须是一个字符串,AsyncStoragekey也必须是一个字符串。

  • 写入一对key-value
static setItem(key: string, value: string, callback:(error))

写入一对key-value,注意写入时value必须是字符串,非字符串数据必须先转换为字符串后再写入(例如我们要写入的数据是一个数组或JS对象,那就必须先用JSON.stringtify()方法把它们转换成JSON字符串后再写入)。写入期间如果发生任何错误,会有一个error作为callback的第一个参数带出。

  • 读取某个key对应的value
static getItem(key: string, callback:(error, result))

读取某个key对应的value,并将读取到的结果作为callback的第二个参数带出(自然如果读取的数据是数组或JS对象,因为我们存进去的时候存的是JSON字符串嘛,所以读取的时候就要用JSON.parse()把它们再转换回数组或JS对象)。读取期间如果发生任何错误,会有一个error作为callback的第一个参数带出。

  • 删除一对key-value
static removeItem(key: string, callback:(error))
  • 写入多对key-value,其中keyValuePairs是字符串的二维数组,如[['key1', 'value1'], ['key2', 'value2']]
static multiSet(keyValuePairs, callback:(errors))
  • 读取多个key对应的value,其中keys是字符串数组,如['k1', 'k2']
static multiGet(keys, callback:(errors, result))
  • 删除多对key-value,其中keys是字符串数组,如['k1', 'k2']
static multiRemove(keys, callback:(errors))
  • 获取所有的key
static getAllKeys(callback:(error, keys))
  • 清空AsyncStorage
static clear(callback:(error))

2. 怎么使用AsyncStorage

原先的导入方法是:

import {AsyncStorage} from "react-native";

但是RN准备从react-native库中移除AsyncStorage,推荐我们如下的使用方法:

  • 导入@react-native-community/async-storage
yarn add @react-native-community/async-storage
  • link一下
react-native link @react-native-community/async-storage
  • 导入AsyncStorage
import AsyncStorage from '@react-native-community/async-storage';
  • 写入数据
_whiteData() {
    AsyncStorage.setItem('key', 'value', error => {
        if (error) {
            console.log('写入数据出错:', error);
        } else {
            console.log('写入数据成功');
        }
    });
}
  • 读取数据
_readData() {
    AsyncStorage.getItem('key', (error, value) => {
        if (error) {
            console.log('读取数据出错:', error);
        } else {
            console.log('读取数据成功:', value);
        }
    });
}
  • 删除数据
_deleteDate() {
    AsyncStorage.removeItem('key', error => {
        if (error) {
            console.log('删除数据出错:', error);
        } else {
            console.log('删除数据成功');
        }
    });
}

三. 离线缓存的实现


离线缓存就是基于网络请求和数据持久化实现的,将请求url作为key,将请求到的数据作为value存在本地。

1. 为什么要做离线缓存

  • 提升用户体验

我们无法保证每个用户的网络状况都非常好,能够快速地加载出界面并使用,所以我们可以做离线缓存来提升用户体验。

  • 节省流量

节省服务器的流量,比如我们做了一个视频播放的App,所有视频的视频源都存放在我们的服务器上,如果用户看过的视频还要去服务端请求的话,这样对服务器造成的压力是比较大的,特别是访问量大的时候,再者服务器的带宽和流量都是花钱的,所以如果能做个离线缓存的话可以节省服务器的流量。

节省用户手机的流量,用户看多的视频没必要再让人家去请求花一遍流量了。

2. 几种离线缓存策略

常见的离线缓存策略有如下三种。

  • 优先读取缓存型:客户端发起网络请求,我们拿到这个请求,优先读取缓存数据,如果缓存数据存在并且未过期则直接加载,否则从服务端请求最新数据并更新缓存。

  • 优先读取网络型:客户端发起网络请求,总是优先从服务端请求数据,请求到数据后缓存到本地,只有当网络出现故障时才从本地读取缓存数据。

  • 同时读取缓存和网络型:客户端发起网络请求,同时加载缓存和服务端数据,一般来说缓存肯定比服务端数据加载的快,所以其实就是先加载缓存数据,然后等网络数据请求回来后刷新页面的数据并更新本地的缓存数据。(感觉刷的那一下怪怪的)

各种离线缓存策略都有自己的优劣和适用场景,也就是说具体适用哪种离线缓存策略要靠我们来衡量,如果页面数据对实时性要求不是那么高,就使用优先读取缓存数据型,否则可以使用优先读取服务端数据型。

3. “优先读取缓存型”策略的实现

这里编写了一个工具类,针对App中所有的网络请求,实现了“优先读取缓存型”策略,于是以后但凡某个请求需要使用到离线缓存,就可以拿这个类去请求数据了,不用离线缓存的请求可以直接用网络请求的工具类。具体的思路和注释都在代码里。

// ProjectRequestWithCache.js

import AsyncStorage from '@react-native-community/async-storage';

export default class ProjectRequestWithCache {
    /**
     * 请求数据,优先读取缓存数据型策略的思想就在这个方法里实现
     *
     * 这里再写一下该策略的思想,方便对比代码理解
     * 1、客户端发起网络请求,我们拿到这个请求的url
     * 2、优先读取缓存数据
     *      读取缓存数据成功,如果缓存数据存在并且未过期,则直接加载,
     *      读取缓存数据失败、缓存数据不存在或已过期,则请求网络数据并更新缓存。
     */
    static fetch(url) {
        // 因为我们是对离线缓存做封装,
        // 所以这里就用Promise对象包装了一下异步操作,这样就可以把异步操作成功的数据或失败的信息传递出去,外界就可以通过.then或者.catch来做回调了
        return new Promise((resolve, reject) => {
            // 优先读取缓存数据
            this._fetchLocalData(url)
                .then(wrapData => {
                    // 读取缓存数据成功
                    if (wrapData && this._checkTimestampValid(wrapData.timestamp)) {// 缓存数据存在并且未过期
                        // 直接返回并加载
                        resolve(wrapData.data);
                    } else {// 缓存数据不存在或已过期
                        this._fetchNetworkData(url)
                            .then(data => {
                                // 网络请求成功
                                resolve(data);

                                // 更新缓存
                                this._writeData(url, data);
                            })
                            .catch((error) => {
                                // 网络请求失败
                                reject(error);
                            })
                    }
                })
                .catch(error => {
                    // 读取缓存数据失败
                    this._fetchNetworkData(url)
                        .then(data => {
                            // 网络请求成功
                            resolve(data);

                            // 更新缓存
                            this._writeData(url, data);
                        })
                        .catch((error) => {
                            // 网络请求失败
                            reject(error);
                        })
                })
        });
    }

    /**
     * 读取缓存数据
     */
    static _fetchLocalData(url) {
        return new Promise((resolve, reject) => {
            AsyncStorage.getItem(url, (error, result) => {
                if (!error) {
                    try {
                        // 因为result是个JSON字符串,所以要序列化为JS对象
                        // 把读取成功的结果传递出去
                        resolve(JSON.parse(result));
                    } catch (e) {
                        // 此处代表序列化失败
                        // 把读取失败的信息传递出去
                        reject(e);
                    }
                } else {
                    // 把读取失败的信息传递出去
                    reject(error);
                }
            })
        })
    }

    /**
     * 请求网络数据
     */
    static _fetchNetworkData(url) {
        return new Promise((resolve, reject) => {
            fetch(url)
                .then(response => {
                    if (response.ok) {
                        // 请求到的response其实是一个Response对象,它是一个很原始的数据格式,我们不能直接使用,先获取它的JSON字符串文本格式
                        return response.text();
                    }
                    throw new Error('网络请求失败!');
                })
                .then(responseText => {

                    // 然后把JSON字符串序列化为JS对象
                    const responseJSObj = JSON.parse(responseText);

                    // 把请求成功的数据传递出去
                    resolve(responseJSObj);
                })
                .catch((error) => {
                    // 把请求失败的信息传递出去
                    reject(error);
                })
        })
    }

    /**
     * 存储数据,作为缓存数据
     *
     * 请求的url作为key,请求到的数据作为value
     */
    static _writeData(url, data, callBack) {
        if (!url || !data) return;

        const wrapData = this._wrapData(data);
        // JSON.stringify为字符串
        AsyncStorage.setItem(url, JSON.stringify(wrapData), callBack);
    }

    /**
     * 给原数据包裹一个时间戳,以便用来检查缓存数据是否过期
     */
    static _wrapData(data) {
        return {data: data, timestamp: new Date().getTime()};
    }

    /**
     * 检查缓存数据是否过期,缓存有效期为4个小时
     */
    static _checkTimestampValid(timestamp) {
        const currentData = new Date();
        const targettData = new Date();
        targettData.setTime(timestamp);

        // 月
        if (currentData.getMonth() !== targettData.getMonth()) return false;
        // 日
        if (currentData.getDate() !== targettData.getDate()) return false;
        // 时
        if (currentData.getHours() - targettData.getHours() > 4) return false;

        return true;
    }
}

使用举例。

constructor(props) {
    super(props);

    this.state = {
        data: {},
    };
}
    
_loadData() {
    const url = `https://api.github.com/search/repositories?q=${this.searchText}`;
    ProjectRequestWithCache.fetch(url)
        .then(data => {
            this.setState({
                data: data,
            });
        })
        .catch(error => {
            alert(error);
        })
}

相关文章

  • RN:网络编程、数据持久化与离线缓存的实现

    目录一. 网络编程二. 数据持久化 1. AsyncStorage是什么 2. 怎么使用AsyncStorage三...

  • reactnative 数据持久化(一)

    rn 数据持久化 数据持久化 data 》 手机存储空间 rn rn中比较常用的数据持久化存储方式有两种: Asy...

  • react-native 3 数据存储

    数据存储是开发APP必不可少的一部分,比如页面缓存,从网络上获取数据的本地持久化等,那么在RN中如何进行数据存储呢...

  • IOS 数据持久化—plist文件

    在实际项目开放中数据持久化是程序核心结构之一,适当的对数据进行持久化存储可以实现应用的离线功能,以此提高用户体验。...

  • Android基础-数据存储

    App要是不支持离线查看数据内容,大概会造成极差的用户体验吧 持久化技术 简介 Android内置的实现持久化功能...

  • Redis入门(特点 应用 数据结构 函数)

    Redis特点(C 实现的) 内存存储,查询速度快,经常用作缓存服务器 支持数据持久化,重启会加载持久化的数据,支...

  • React-Native 数据持久化探索

    使用RN也有一段时间了,但是却未试过在RN项目中实现数据的持久化,虽然项目继承了react-redux,但是数据存...

  • Service Worker — 实现离线应用

    用于离线缓存 可以用来实现离线应用,本质上充当应用程序、浏览器和网络之间的代理服务器。 用于实现离线缓存时,它拦截...

  • Java面试——Redis和缓存

    参考资料: Redis的存储类型及底层实现 ?? Redis持久化数据和缓存怎么做扩容? 如果Redis被当做缓存...

  • 数据持久化

    数据持久化及数据更新缓存 常用的8种缓存机制:HTTP缓存, locationStorage, Session S...

网友评论

      本文标题:RN:网络编程、数据持久化与离线缓存的实现

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