react-native-easy-app 详解与使用之(二) fetch

react-native-easy-app是一款为React Native App快速开发提供基础服务的纯JS库(支持 IOS & Android),特别是在从0到1的项目搭建初期,至少能够为开发者减小30%的工做量。前端

react-native-easy-app主要作了这些工做:
1. 对AsyncStorage进行封装,开发者只需几行代码便可实现一个持久化数据管理器。
2. 对fetch进行封装,使得开发者只需关注当前App的先后台交互逻辑和协议,定义好参数设置及解析逻辑便可。
3. 从新封装了RN的View、Text、Image、FlatList 使用得这些控件在适当的时候支持事件或支持icon与文本,能有效减小布局中的嵌套逻辑。
4. 经过设置一个屏幕参考尺寸,重置XView、XText、XImage的尺寸,实现自动多屏适配react

可能有人以为,不一样的App对Http请求的要求各异,第三方库怎么可能作到全面的封装,就算作到了,那也一定会封装过分git

一千我的心中,有一千个哈姆雷特,也许个人思路能给你带来不同的启发也未可知呢?github

网络请求(fetch)

咱们先来看下React native中文网给出的fetch使用示例:web

  • 异步请求(核心代码)
fetch('https://facebook.github.io/react-native/movies.json')
    .then((response) => response.json())
    .then((responseJson) => {
      return responseJson.movies;
    })
    .catch((error) => {
      console.error(error);
    });
  • 同步请求(核心代码)
try {
    // 注意这里的await语句,其所在的函数必须有async关键字声明
    let response = await fetch('https://facebook.github.io/react-native/movies.json');
    let responseJson = await response.json();
    return responseJson.movies;
  } catch (error) {
    console.error(error);
  }

RN平台的fetch请求很简洁,那咱们再看看react-native-easy-app的请求XHttp是否是也能够方便快捷的发送请求呢?npm

  • 异步请求(核心代码)示例 1
import { XHttp } from 'react-native-easy-app';

 XHttp().url('https://facebook.github.io/react-native/movies.json').execute('GET')
   .then(({success, json, message, status}) => {
      console.log(json.movies)
    })
    .catch(({message}) => {
        showToast(message);
    })
  • 同步请求(核心代码)示例 2
import { XHttp } from 'react-native-easy-app';

  const response = await XHttp().url('https://facebook.github.io/react-native/movies.json').execute('GET');
  const {success, json, message, status} = response;
  console.log(json.movies)
  • 异步请求2(核心代码)示例 3
import { XHttp } from 'react-native-easy-app';

   XHttp().url('https://facebook.github.io/react-native/movies.json').get((success, json, message, status)=>{
       console.log(json.movies)
   });

经过执行上面三段示例代码,发现输出了一致的结果(电影列表数组):编程

movies.pngjson

经过对比发现XHttp的使用与React Native平台提供的fetch很类似,其execute('get')方法返回的是一个promise对象,故也能够像fetch同样,发送同步或异步请求。另外还能够经过[method]+回调的形式发送请求。react-native

相比原生fetch请求,XHttp 却返回了多个参数,咱们打印一下示例2中的response看看里面都有啥?输出结果,格式化后以下:api

response.png

  1. success=> [true | false] 请求成功或失败的标识(默认以Http的请求状态码:[ status >= 200 && status < 400 ] 做为判断依据)。
  2. json=> [Json Object | originText] 默认为请求返回的json对象,必要时能够指定返回纯文本字符串(若请求结果为非标准Json,如XML结构或其它)或经过自定义配置指定请求返回的数据结构。
  3. message默认状况下,请求成功时:为[code+url],失败时:则为错误信息[错误信息+code+url],若开发者指定了特定的解析方式,则由开发者制定。
  4. status默认状况下为Http请求的status code,可由开发者制定,返回自定义的业务逻辑请求状态码

经过上面的示例, react-native-easy-app 的 XHttp 能够像使用fetch同样方便快捷的发送Http请求,并且还包含请求码,错误信息,结果也被转化为了json对象,使用咱们发送请求更加方便了。

但在实际的App开发中,咱们Http请求框架的要求不仅是能发送简单的Http请求就能够了,好比说,须要打印请求日志、设置header参数、统一处理解析逻辑,甚至可能处理返回的结构不是标准的json数据等各类需求。

咱们来看看 react-native-easy-app 的 XHttp 能知足咱们哪些需求:
注:上面三个示例的请求方式各有所长,下文发送请求示例的地方我都选择使用请求示例 3的方式举例

  • 需求 1 :能支持get、post、put、delete等基本经常使用类型的请求
    • 框架会自动根据输入的请求类型,自动会处理请求的body有无问题
    • 一、经过XHttp 的execute('method')方式发送请求天然是没有问题
    • 二、经过method + 回调的形式(知足90%的状况),我问下的状况怎么办?不用担忧框架提供了另外一种方式实现,即:
XHttp().url('https://facebook.github.io/react-native/movies.json').request('HEAD', (success, json, message, status) => {
    console.log(json.movies);
})

  • 需求 2:能支持经常使用的contentType设置,如 application/json、multipart/form-data、application/x-www-form-urlencoded等
    • 固然并不仅是简单的传个参数而已,必须能根据请求contentType按正常的方式处理body,若是contentType若为multipart/form-data,则使用FormData去接收拼接开发者传入的参数
    • 一、 XHttp 有三种方式设置contentType,三种经常使用的方式被提取了出来,以下分别是:直接设置;经过header设置;经过方法直接指定。开发者设置了相应的方式以后,就能够放心的发送Http请求了,剩下的框架会处理(下面示例为:上传图片设置):


  • 需求 3:能支持超时设置;支持日志打印;支持返回非标准Json以及baseUrl的拼接
    • 请求超的原理是经过 Promise.race 实现;
    • 1.因为超时请求并不彻底属于某个特定的请求,故引入了一个公共配置对象:XHttpConfig,开发者能够经过两种试设置请求超时配置,以下:
import { XHttpConfig } from 'react-native-easy-app';

XHttpConfig().initTimeout(300000); //全局配置,设置全部Http请求的超时时间为30秒

XHttp().url('https://facebook.github.io/react-native/movies.json').timeout(15000) //设置当前请求超时间为15秒
    .get((success, json, message, status) => {
    })
  • 二、日志打印也是经过 XHttpConfig().initHttpLogOn(true) 设置为 true 便可,设置完成后,咱们发送请求,看看控制台的输出日志:
XHttpConfig().initHttpLogOn(true);
XHttp().url('https://facebook.github.io/react-native/movies.json').get((success, json, message, status) => {
})

httplog.png

能够看出控制台打印出了详细的日志,是否是很方便?

  • 三、如今的移动开发99%的状况下先后台交互都是使用的json格式数据,但很难保证一些特殊状况下,App不使用非标准json数据格式的Http请求。好比须要请求一些老网站或者使用一些第三方开放的老接口。这时候只须要指定返回纯文件数据便可,下面找一个返回xml格式的接口,请求看看结果:
let url = 'http://www.webxml.com.cn/WebServices/MobileCodeWS.asmx/getDatabaseInfo'
XHttp().url(url).pureText().get((success, text, message, status) => {
    console.log('XML data', text)
})

控制台输出结果以下(经过XHttp的 pureText() 指定返回的数据以纯文本返回):

httpXml.png

  • 四、至于baseUrl的拼接,则是为了在App开发中,减小没必要要的baseUrl的重复使用(程序经过判断传入的url是不是完整按需拼接BaseUrl),使用方法以下:
import { XHttpConfig, XHttp } from 'react-native-easy-app';

XHttpConfig().initBaseUrl('http://www.webxml.com.cn/WebServices/');
XHttp().url('MobileCodeWS.asmx/getDatabaseInfo').get((success, text, message, status) => {
    console.log('XML data', text)
})

  • 需求 4:能自由设置公共的params、headers;发送Http请求的时候,也能自由设定当前请求的header及param数据。
import { XHttpConfig, XHttp } from 'react-native-easy-app';

XHttpConfig().initHttpLogOn(true)
    .initBaseUrl('https://facebook.github.io/')
    .initContentType('multipart/form-data')
    .initHeaderSetFunc((headers, request) => {
        headers.headers_customerId = 'headers_CustomerId001';
        headers.headers_refreshToken = 'headers_RefreshToken002';
    })
    .initParamSetFunc((params, request) => {
        params.params_version = 'params_version003';
        params.params_channel_code = 'params_channel_code004';
        params.testChannel = 'testChannel005';
    });

XHttp().url('react-native/movies.json')
    .header({'Content-Type': 'application/json', header_type: 'header_type006'})
    .param({paramUserName: 'paramUserName007', testChannel: 'testChannel008'})
    .post((success, text, message, status) => {
    })

从代码中能够看出经过XHttpConfig配置,咱们设置了公共的heders、params,而后在经过XHttp发送请求时,又设置了特定的header和param的值,同时了修改了contentType的类型,并改成post请求,执行代码咱们看看控制台日志内容:

common_params.png

经过控制台打印的日志,咱们能够很清晰的看到,参数从001~008全部的参数(除了005)都能有效设置到请求当中。但为何公共参数 params.testChannel = 'testChannel005'; 的设置没有生效呢,实际上是由于,XHttp中的接口请求的私有参数中也设置了一个:testChannel: 'testChannel008' 的参数,二者的Key相同,因此被接口私有参数给覆盖了(细心的同窗也能够发现,日志中'Content-Type': 'application/json',contentType的类型也被覆盖了),这说明了接口的私有参数具备更高的优先级,这是合理的同时也使接口的请求更灵活方便。


  • 需求 5:能支持自定义数据解析,这也是最重要的。
    每一个app都有一套先后台数据交互方式,对于返回的数据都有统一固定的格式:方便前端解析处理,如cryptonator.com网站提供的比特币查询接口,接口url:https://api.cryptonator.com/api/ticker/btc-usd。咱们先经过postman请求一下:

request_postman.png

返回的数据格式以下:

{
  "ticker": {
    "base": "BTC",
    "target": "USD",
    "price": "5301.78924881",
    "volume": "179358.70555921",
    "change": "-21.18183054"
  },
  "timestamp": 1584291183,
  "success": true,
  "error": ""
}

能够看出,接口返回的数据结构中,有三个主要字段:

  1. success 接口逻辑成功与失败的判断依据。
  2. error 接口若失败时,包含错误信息。
  3. ticker 接口返回的主要数据的主体。

之前面XHttp发送请求,接口的成功与否的判断依然是http的status来判断,显示达不到要求,请求cryptonator.com网站api数据统一解析的基本要求,那怎么自定义数据解析呢?咱们试试看。

import { XHttpConfig, XHttp } from 'react-native-easy-app';

XHttpConfig().initHttpLogOn(true)
    .initBaseUrl('https://www.cryptonator.com/api/')
    .initParseDataFunc((result, request, callback) => {
        let {success, json, message, status} = result;
        callback(success && json.success, json.ticker || {}, json.error || message, status);
    });

XHttp().url('ticker/btc-usd').get((success, json, message, status) => {
    console.log('success = ' + success);
    console.log('json    = ' + JSON.stringify(json));
    console.log('message = ' + message);
    console.log('status  = ' + status);
});

咱们再看下控制台输出的请求日志与Http请求打印的4个标准参数的内容:

custom_parse_data_log.png

custom_parse_data.png

发现没有,json对应的值就是返回的数据结构中:ticker对应的数据。其它字段并不能反映出来,由于数据恰好与默认判断条件吻合或为空。这是怎么实现的呢?

由于经过XHttpConfig的initParseDataFunc方法,咱们从新定义了,接口请求返回的标准字段的值:

  1. success => success && json.success 只有当接口请求与返回的成功标记同时为true的时候才认为是成功
  2. json => json.ticker 直接读取json.ticker的值(若为空,则返回一个没有任何属性对象)
  3. message => json.error || message 优先获取接口返回的错误信息(若为空,则读取Http请求的错误信息)
  4. status => status 因为些api并无code判断标记,故依然使用Http的status

这样Http请求返回的参数自定义问题就解决了,这时候可能有人会说:个人app不仅是请求一个后台或者还要请求第三方接口,不一样的后台返回的数据结构也彻底不同,这种状况下么处理?不用担忧,这种状况也是有解的:

  • 办法一(非标准接口较少的状况):
    好比说,个人请求以cryptonator.com网站的api为主,偶尔要请求域名查询接口: https://api.domainsdb.info/v1/domains/search?domain=zhangsan&zone=com,这个时候,我能够依然保持前面的自定义解析方式不变,在请求域名查询的时候,增长一个标记:
FHttp().url('https://api.domainsdb.info/v1/domains/search')
   .param({domain: 'zhangsan', zone: 'com'})
   .contentType('text/plain')
   .rawData()
   .get((success, json, message, status) => {
       if (success) {
           console.log('rawData', JSON.stringify(json))
       } else {
           console.log(message)
       }
   })

接口请求打印的日志为:

rawData.png

请求依然成功,各参数也没有问题,由于在发送Http请求的时候增长了一个标记rawData(),这个标记就是用于特殊处理的,标记当前Http请求须要返回原始的,不作任何解析的数据(设置此标记,会自动忽略用户自定义的数据解析方式)

  • 办法二(也有可能一个App要请求多个不一样的平台或者新老版本过渡,并且不一样风格的接口数量还不在少数),同时在这种状况下可能请求的参数风格,公共参数也有不一样的要求,这就更复杂了,这种状况可否处理?答案是确定的:

假设当前App要请求三个平台:分别为SA,SB,SC,这三个平台要求不一样的公共参数(包括header),且返回的数据结构也彻底不一致,这时候咱们能够这样处理,配置与请求均可以彻底独立的实现:

import { XHttpConfig, XHttp } from 'react-native-easy-app';

XHttpConfig('SA').initHttpLogOn(true) ...

XHttpConfig('SB').initHttpLogOn(true) ...

XHttpConfig('SC').initHttpLogOn(true) ...

const url = 'https://facebook.github.io/react-native/movies.json';

XHttp().serverTag('SA').url(url) .get((success, json, message, status) =>{
});

XHttp().serverTag('SB').url(url) .get((success, json, message, status) =>{
});

XHttp().serverTag('SC').url(url) .get((success, json, message, status) =>{
});

就是这么简单,配置与请求能够经过serverTag来区别,默认状况下使用同一个配置,但若指定了新的serverTag,发送Http请求时就能够经过serverTag来指定使用哪一个Http请求的配置,这样同一个app里面,请求不一样的服务器,以及处理不一样服务器返回的数据也彻底没有压力。

经过上面的例子,咱们能够看出,XHttpConfig的三个公共配置方法:initHeaderSetFunc、initParamSetFunc、initParseDataFunc 是一个面向切面的编程模式,这些方法还有一个共同的参数request(第二个参数)里面包含了请求的全部原始信息,所以能够有更多的想象空间,就等你去探索。


  • 可能部分同窗以为,框架的参数设置挺方便,但数据的解析我想彻底本身实现能够么?固然能够,经过fetch方法,返回的是原fetch请求的promise,框架不作任何处理:

  • 也有同窗想,框架的解析很方便,我想彻底使用框架的解析,但有些参数是放在header里面,我怎么才能在解析数据的时候取到response的header数据呢?这个问题也不用担忧,在全部示例中,我列表的解析回调的参数都是4个:(success, json, message, status),但实际上有5个参数,第5就是response,它就是fetch返回的reponse,你能够从里取到任何想要的数据,包括headers
const url = 'https://facebook.github.io/react-native/movies.json';

XHttp().url(url).get((success, json, message, status, response) => {
    console.log(JSON.stringify(response.headers))
});

const {success, json, message, status, response} = await XHttp().url(url).execute('GET');
console.log(JSON.stringify(response.headers))
  • 也有同窗可能想到有一种应用场景oauth2须要特别处理:
  1. 发送请求req1,由于accessToken失效而请求失败
  2. 程序经过refreshToken从新获取到了新的accessToken
  3. 拿着新的accessToken从新请求req1

这种应用场景怎么处理呢?

XHttpConfig()
    .initHttpLogOn(true)
    .initBaseUrl(ApiCredit.baseUrl)
    .initContentType(XHttpConst.CONTENT_TYPE_URLENCODED)
    .initHeaderSetFunc((headers, request) => {
        if (request.internal) {
            Object.assign(headers, AuthToken.baseHeaders());//添加基础参数
            headers.customerId = RNStorage.customerId;
            if (RNStorage.refreshToken) {//若refreshToken不为空,则拼接
                headers['access-token'] = RNStorage.accessToken;
                headers['refresh-token'] = RNStorage.refreshToken;
            }
        }
    })
    .initParamSetFunc((params, request) => {
        if (request.internal && RNStorage.customerId) {
            params.CUSTOMER_ID = RNStorage.customerId;
        }
    }).initParseDataFunc((result, request, callback) => {
    let {success, json, response, message, status} = result;
    AuthToken.parseTokenRes(response);//解析token
    if (status === 503) {//指定的Token过时标记
        this.refreshToken(request, callback)
    } else {
        let {successful, msg, code} = json;
        callback(success && successful === 1, selfOr(json.data, {}), selfOr(msg, message), code);
    }
});
static refreshToken(request, callback) {
    if (global.hasQueryToken) {
        global.tokenExpiredList.push({request, callback});
    } else {
        global.hasQueryToken = true;
        global.tokenExpiredList = [{request, callback}];
        const refreshUrl = `${RNStorage.baseUrl}api/refreshToken?refreshToken=${RNStorage.refreshToken}`;
        fetch(refreshUrl).then(resp => {
            resp.json().then(({successful, data: {accessToken}}) => {
                if (successful === 1) {// 获取到新的accessToken
                    RNStorage.accessToken = accessToken;
                    global.tokenExpiredList.map(({request, callback}) => {
                        request.resendRequest(request, callback);
                    });
                    global.tokenExpiredList = [];
                } else {
                    console.log('Token 过时,退出登陆');
                }
            });
        }).catch(err => {
            console.log('Token 过时,退出登陆');
        }).finally(() => {
            global.hasQueryToken = false;
        });
    }
};

在这里我就不作详细说明了直接贴代码,详细的请你们能够直接阅读源码或者参考react-native-easy-app库对应的示例项目,至于原理是:在请求的时候,将初请求的方法引用保存到了request中,并命名为resendRequest,若获取到新的token以后,从新请求一遍resendRequest方法,传入原来的参数便可。


可能有同窗以为react-native-easy-app封装XHttp与XHttpConfig的方法与参数太多了,根本没办法记住,框架虽好却不便于使用,这个目前可能须要你们参考示例项目来写了(后面我会完善说明文档)。

固然你们有没有发现,在使用这些库方法的时候,代码有提示呢?那就对了。由于我为主要的方法增长了dts描述文档,因此在写代码过程当中,若是不记得方法名参数直接经过代码自动提示来写就好了(自动提示在webStorm上的体验更好):

react-native-easy-app 详解与使用之(三) View,Text,Image,Flatlist

想进一步了解,请移步至npm或github查看react-native-easy-app,有源码及使用示例,待你们一探究竟,欢迎朋友们 Star!

若是有任何疑问,欢迎扫码加入RN技术QQ交流群

相关文章
相关标签/搜索