在React Native開發過程中,幾乎所有的app都需要使用到Http請求,是以fetch的封裝必不可少,由于不同app的請求參數,解析規則,token機制等完全不一樣,是以在大多數App開發中,前背景Http請求的實作都是開發者自己封裝的。
封裝一個前背景Http請求實作需要多久?
可能有人回答是1小時,也有3、5小時甚至更長時間的,或者也有說先這樣封裝個大概,等到需求不滿足的時候再改。
花費1小時的時間不一定短,花費3、5的時間也不一定算長,具體要看前背景互動的複雜程度與開發者對互動實作的封裝程度。
那這裡我們就引出了一個問題了,我們通常說的app的Http請求【封裝】,到底封裝的是什麼,我們需要做哪些工作,能使用得app的接口請求更簡單,易用且有較高的靈活性?在我看來這個“封裝”主要分兩個部分:
- 資料交換 層面的封裝,即:
-
- 實作前背景的互通,支援伺服器要求的資料交換類型、格式等
- 調用者可以自由設定請求的header、params等參數,程式根據不同的設定也能保證請求能正确的發送給服務端并傳回相應的結果
- 支援逾時、日志列印等一些基本功能
業務邏輯 層面的封裝,即:
- 入參:公共部分header、params的參數處理,避免在具體接口請求是傳入不必要與接口無關的參數
- 出參:對背景傳回的資料按約定好的規則做一層基礎解析處理,避免在具體接口資料解析的時候做一些無意義的操作
從投入的時間上來看:
第一部分基本上要花掉開發者80%以上的時間來封裝
第二部分需要消耗的時間可能不足20% 【以此推算,按上面1個小時的封裝時間,用在邏輯封裝部分的時間也就12分鐘左右😝】
我們再回頭看一下,第一部分的【資料交換】封裝是否涉及到具體業務邏輯呢?答案是:沒有。
既然沒有我們為什麼不把第一部分的封裝交給第三方架構呢,我們隻需要做第二部分的封裝多省事,有這樣第三方架構麼?
答案是:有的,
react-native-easy-app就可以實作【前背景資料交換】層面的封裝,通過這個開源庫,我們就隻需要實作涉及【App業務邏輯】層面的封裝即可。
為驗證
的實用性,在這裡我們先來構想一個業務邏輯層面封裝的需求:
- 請求接口的公共headers參數有:
- version、channelCode、model、platform (所有接口)
- accessToken、refreshToken、customerId (登入後額外增加)
- 請求接口的公共params參數有:
- customerId (登入後額外增加)
-
背景傳回的資料結構示例如下:
{ data: {}, successful:1, msg: 'request msg', code: 'xxx'}
- 請求狀态碼為503的時候表示accessToken過期,accessToken過期的情況下,需要重新擷取新的accessToken并重新整理因accessToken過期導緻請求失敗的接口
- accessToken、refreshToken在登入成功後的response的headers中傳回。
對于以上業務邏輯層面的需求,看看通過
我們可以怎麼做。
XHttpConfig().initHttpLogOn(true)
.initHeaderSetFunc((headers) => {
headers['model'] = 'xiao mi';
headers['version'] = '1.0.0';
headers['platform'] = Platform.OS;
headers['channelCode'] = 'channelOfficial';
if (isLogin()) {
headers['customerId'] = RNStorage.customerId;
headers['accessToken'] = RNStorage.accessToken;
headers['refreshToken'] = RNStorage.refreshToken;
}
})
.initParamSetFunc(params => {
if (isLogin()) {
params['customerId'] = RNStorage.customerId;
}
})
.initParseDataFunc((result, request, callback) => {
let {success, json, message, status, response} = result;
if (status === 503) {// accessToken過期标記
this.refreshToken(request, callback);
} else {
let {data, successful, msg, code} = json;
callback(success && successful === 1, data || {}, msg || message, code, response);
}
});
accessToken重新請求的實作及對失敗接口的重新整理:
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;
});
}
};
就這樣對目前構想的app的邏輯層面的封裝就實作了(實作上面的代碼約70行,也許要超過20分鐘 😆😝,但相較于以前從零開的封裝,是不是節約了大量的時間呢?)是不是清晰明了。當然,這隻是代碼片段,沒有實際操作,就沒辦法證明上面的代碼實作是實際有效的。
為了示範,先用
react native init HttpTestDemo
建立一個RN項目:
示例項目:HttpTestDemo修改并删除不必要的布局或資源,結果如下:
假定有三個接口,分别為 api/login、api/userInfo 、api/refreshToken (為了省事,接口都以json檔案替代)
- api/login 有兩個必傳參數:[userName、userPass];請求内容類型為:application/x-www-form-urlencoded;post請求
- api/userInfo 無參數;請求内容類型為:application/json;get請求
- api/refreshToken 必須參數refreshToken;請求内容類型為:application/json;get請求
- 按 react-native-fast-app 的說明文檔,安裝庫:
npm install react-native-fast-app --save
- 定義一個持久化對象,用于儲存accessToken,customerId等參數:
export const RNStorage = {// 持久化資料清單
customerId: undefined,//客戶ID
accessToken: undefined,//OAuth2.0 accessToken
refreshToken: undefined,//OAuth2.0 refreshToken
baseUrl: undefined,
userInfo: undefined,
hasLogin: false,
};
3.在頁面的構造方法時調用 RNStorage的初始化操作;初始化完成之後,調用Http請求XHttpConfig的【業務邏輯】層初始化方法,這樣就完成了,現在就可以調用接口了。
- 調用登入接口:(由于使用json檔案的形式隻能使用get請求)
import { XHttp } from 'react-native-easy-app';
login = () => {
let params = {userName: 'zhangsan', userPass: '123456a'};
XHttp().url('api/login').param(params).formEncoded().get((success, json, message, status, resonse) => {
if (success) {
if (resonse.headers && resonse.headers.map) {
RNStorage.accessToken = resonse.headers.map['x-oss-meta-accesstoken'];
RNStorage.refreshToken = resonse.headers.map['x-oss-meta-refreshtoken'];
}
RNStorage.customerId = json.customerId;
RNStorage.hasLogin = true;
this.setState({data: JSON.stringify(json)});
} else {
console.log('失敗', message);
}
});
};
調用接口,通過架構自帶的日志功能,可以看到,該拼的參數都拼接了,從header中也擷取到了token
- 調用擷取使用者個人資訊接口:
import { XHttp } from 'react-native-easy-app';
queryUserInfo = () => {
XHttp().url('api/userInfo').formJson().get((success, json, message) => {
if (success) {
RNStorage.userInfo = json;
this.setState({data: JSON.stringify(json)});
} else {
console.log('失敗', message);
}
});
};
調用接口,通過架構自帶的日志功能,可以看到accessToken、refreshToken也正确的拼接了。
由于沒有合适的伺服器,token過期的情況就不示範了,隻要請求refreshToken的接口正常請求就不會有問題。
至此一個完整的App 【業務邏輯】層面的封裝就完全實作了,從Http請求的配置到,refreshToken的重新請求到重新整理失敗接口,一共大概隻用了70行代碼左右,是不是相較于之前從零開始的fetch封裝簡單容易多了,節約了大量的封裝時間呢?
擔心架構的靈活性?請參考
react-native-easy-app 詳解與使用之(二) fetch并且react-native-easy-app 開源庫并不隻有Http請求的封裝,還有更多功能,有興趣的同學可以檢視此欄目的
其它文章,你肯定會有更多收獲。
目前示例項目連結:
HttpTestDemo