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
- success=> [true | false] 請求成功或失敗的標識(默認以Http的請求狀態碼:[ status >= 200 && status < 400 ] 做爲判斷依據)。
- json=> [Json Object | originText] 默認爲請求返回的json對象,必要時能夠指定返回純文本字符串(若請求結果爲非標準Json,如XML結構或其它)或經過自定義配置指定請求返回的數據結構。
- message默認狀況下,請求成功時:爲[code+url],失敗時:則爲錯誤信息[錯誤信息+code+url],若開發者指定了特定的解析方式,則由開發者制定。
- 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": "" }
能夠看出,接口返回的數據結構中,有三個主要字段:
- success 接口邏輯成功與失敗的判斷依據。
- error 接口若失敗時,包含錯誤信息。
- 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方法,咱們從新定義了,接口請求返回的標準字段的值:
- success => success && json.success 只有當接口請求與返回的成功標記同時爲true的時候才認爲是成功
- json => json.ticker 直接讀取json.ticker的值(若爲空,則返回一個沒有任何屬性對象)
- message => json.error || message 優先獲取接口返回的錯誤信息(若爲空,則讀取Http請求的錯誤信息)
- 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須要特別處理:
- 發送請求req1,由於accessToken失效而請求失敗
- 程序經過refreshToken從新獲取到了新的accessToken
- 拿着新的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交流羣