React Native在用户网络故障时自动调取缓存

V3WWCR.png

App 往往都有缓存功能,例如常见的新闻类应用,如果你关闭网络,你上次打开 App 加载的数据还在,只是不能加载新的数据了。

# 集中处理请求

如果你 fetch 数据的页面有多个,不集中处理的话每个页面都要单独进行缓存处理。那么,如何对 http 请求进行集中处理?
在 WebApp 中常见的做法就是将请求放在action里面,例如VuexRedux。但是在业务逻辑较少的 App 中,我们往往可能不需要Redux。这时就需要我们自己对集中请求进行封装。

# 封装 AsyncStorage

AsyncStorage 只能存取字符串,我们需封装一下,让它能存取json:

import { AsyncStorage } from 'react-native'

class Storage {
  set({ key, val }) {
    return AsyncStorage.setItem(key, JSON.stringify(val))
  }
  get(key) {
    return AsyncStorage.getItem(key).then((val) => {
      return JSON.parse(val)
    })
  }
  remove(key) {
    return AsyncStorage.removeItem(key)
  }
  clear() {
    return AsyncStorage.clear()
  }
}

export default new Storage()

命名为storage.js

# 封装公共请求函数

在网络故障时获取 storage 里的内容,网络良好时更新 storage。

import axios from 'axios'
import storage from './storage'
import apiList from './apiList'
import { NetInfo, ToastAndroid } from 'react-native'
/**
 * @param {String} api 接口名称
 * @param {Object} [replace={}] 替换url中的{}包裹的参数
 * @param {Object} [data={}] 传给服务端的数据
 * @param {Object} [headers={}] http请求头参数
 * @return {Promise} 返回promise
 */
const $http = async ({ api, replace, data, headers }) => {
  let regExp = /\{ *([\w_\-]+) *\}/g,
    url = apiList[api].url,
    replaceList = url.match(regExp)
  if (replaceList) {
    replaceList.forEach((i) => {
      let key = i.slice(1, i.length - 1)
      url = url.replace(i, replace[key])
    })
  }
  let netStatu = await NetInfo.getConnectionInfo()
  let result
  if (['none', 'unknown'].includes(netStatu.type)) {
    ToastAndroid.show('请检查您的网络连接', ToastAndroid.SHORT)
    result = (await storage.get(api)) || null
  } else {
    try {
      let { data } = await axios({
        method: apiList[api].method,
        url: url,
        data: data,
        headers: headers
      })
      result = data
    } catch (err) {
      ToastAndroid.show(err.message, ToastAndroid.SHORT)
      result = (await storage.get(api)) || null
    }
    await storage.set({
      key: api,
      val: result
    })
  }
  return result
}

export default $http

命名为service.js

# 请求配置

为标示请求唯一性,我们需给每个请求取一个名称:

export default {
  GET_NEWS_LIST: {
    url: '/my/news?pageNum={pageNum}&pageSize={pageSize}',
    method: 'get'
  }
}

命名为apiList.js

# 请求调用

我们可以模仿一下VuexRedux的 action:

import $http from './service'

export async function getNewsList(pageNum, pageSize) {
  return await $http({
    api: 'GET_NEWS_LIST',
    replace: {
      pageNum,
      pageSize
    }
  })
}

命名为serviceAction.js 调用:

import {getNewsList} from './serviceAction'

// ...
async componentDidMount() {
    let newsList = await getNewsList(1)
    this.setState({
        newsList
    })
}
// ...