react-native-easy-app 详解与使用之(二)fetch - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
rufeng008
V2EX    程序员

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

 
  •   rufeng008 2020-05-14 12:27:20 +08:00 1260 次点击
    这是一个创建于 1994 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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 的尺寸,实现自动多屏适配

    可能有人觉得,不同的 App 对 Http 请求的要求各异,第三方库怎么可能做到全面的封装,就算做到了,那也必定会 封装过度

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

    网络请求( fetch )

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

    • 异步请求(核心代码)
    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是不是也可以方便快捷的发送请求呢?

    • 异步请求(核心代码) 示例 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.png

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

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

    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 有无问题
      • 1 、通过 XHttp 的 execute('method')方式发送请求自然是没有问题
      • 2 、通过 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 去接收拼接开发者传入的参数
      • 1 、XHttp 有三种方式设置 contentType,三种常用的方式被提取了出来,如下分别是:直接设置;通过 header 设置;通过方法直接指定。开发者设置了相应的方式之后,就可以放心的发送 Http 请求了,剩下的框架会处理(下面示例为:上传图片设置):

    contentType.png


    • 需求 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) => { }) 
    • 2 、日志打印也是通过 XHttpConfig().initHttpLogOn(true) 设置为 true 即可,设置完成后,我们发送请求,看看控制台的输出日志:
    XHttpConfig().initHttpLogOn(true); XHttp().url('https://facebook.github.io/react-native/movies.json').get((success, json, message, status) => { }) 

    httplog.png

    可以看出控制台打印出了详细的日志,是不是很方便?

    • 3 、现在的移动开发 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

    • 4 、至于 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 不只是请求一个后台或者还要请求第三方接口,不同的后台返回的数据结构也完全不一样,这种情况下么处理?不用担心,这种情况也是有解的:

    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,框架不做任何处理:

    parse_native.png

    • 也有同学想,框架的解析很方便,我想完全使用框架的解析,但有些参数是放在 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 上的体验更好):

    提示 1.png

    提示 2.png

    提示 3.png

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

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

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

    qq_qrCode.jpg

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1029 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 19:15 PVG 03:15 LAX 12:15 JFK 15:15
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86