1、基本概述
由京東凹凸團隊開源的小程序框架Taro (https://taro.aotu.io/),以及對應UI框架Taro-UI (https://taro-ui.jd.com/#/),是一套遵循 React 語法規範的 多端開發 解決方案。根據官方介紹:
現如今市面上端的形態多種多樣,Web、React-Native、微信小程序等各種端大行其道,當業務要求同時在不同的端都要求有所表現的時候,針對不同的端去編寫多套代碼的成本顯然非常高,這時候只編寫一套代碼就能夠適配到多端的能力就顯得極爲需要。
使用 Taro,我們可以只書寫一套代碼,再通過 Taro 的編譯工具,將源代碼分別編譯出可以在不同端(微信/百度/支付寶/字節跳動/QQ小程序、快應用、H5、React-Native 等)運行的代碼。
官方提供了對應的腳手架生成方法,本文基於官方腳手架進一步封裝,幫助大家更好的利用Taro進行日常開發。github代碼地址:https://github.com/zhengchangshun/myTaro
2、模塊介紹
├── dist 編譯結果目錄
├── config 配置目錄
| ├── dev.js 開發時配置
| ├── index.js 默認配置
| └── prod.js 打包時配置
├── src 源碼目錄
| ├── components 全局組件
| | | ├── DateTimePicker 日期時間組件
| | | ├── GenerateDetail 通過配置生成詳情的組件
| | | ├── PickerSelector 下拉組件的封裝
| ├── lib 公共類
| | | ├── constant 常量
| | | ├── envUtil 依賴於測試、生產環境的配置和方法
| | | ├── request http請求的封裝
| | | ├── interceptors 針對http請求的response的處理
| | | ├── utils 常用公共方法的封裝
| ├── pages
| | ├── index index 頁面目錄
| | | ├── index.js index 頁面邏輯
| | | └── index.css index 頁面樣式
| ├── services 用於存放http請求的api
| ├── app.css 項目總通用樣式
| └── app.js 項目入口文件
└── package.json
1、dist:編譯後生成的代碼,具體參考官方文檔。
2、config:腳手架生成,這個配置是整個應用的全局的配置,具體參考官方文檔。 這裏用到了別名的配置alias,可以避免書寫多級相對路徑。配置別名後,爲了讓編輯器(VS Code)不報錯,並繼續使用自動路徑補全的功能,需要添加jsconfig.json文件。webstorm中添加自定義配置文件webstrom.config.path.js,並在 setting - languages & Frameworks - JavaScript - Webpack中添加該自定義配置文件;
3、src:源碼。
3.1、componnet 自定義的全局組件
DateTimePicker - 日期時間組件(官方組件庫中沒有時間日期組件)
GenerateDetail - 可以通過配置生成詳情
PickerSelector - 下拉選擇組件
3.2、lib 公共類封裝
constant - 存放常量,枚舉
envUtil - 與env相關的配置和方法
request - http請求的封裝
interceptors - 可以通過配置生成詳情
utils - 常用方法的封裝
3.3、page頁面:正常業務需求開發的頁面。
3.4、services:接口請求統一在services下管理,可以根據各個模塊的不同,分成多個js文件。
3、詳細介紹
這部分重點介紹下lib下的envUtil、request和interceptors。
3.1、envUtil.js
envUtil.js主要存放的是與環境相關的配置和方法。Taro框架提供了變量: process.env.NODE_ENV,可以獲取當前環境參數,可用於判斷是測試、生產環境等。
通常情況下,在前後端分離模式下進行開發,在代碼中,前端請求url一般是相對路徑,http請求會存在跨域,常見的解決方法是通過配置nginx服務器的反向代理。在nginx中通過url的關鍵字做正則匹配後,決定轉發到具體的服務器,正常情況下,我們會分別部署測試環境和生產環境,測試環境和生產環境的轉發域名會有不同。簡單來講如下:
graph LR
測試環境http請求 --> 測試服務器
graph LR
正式環境http請求 --> 正式服務器
通過上述簡單的介紹,我們瞭解到,微信小程序如果需要發送http請求成功,需要完成以下兩點:
1、接口服務器可訪問
2、當前環境下的接口請求能夠轉發到對應環境下的接口服務器
對於第一個問題,解決方法:在微信公衆平臺小程序管理後臺添加request請求的白名單。針對第二個問題,可以通過process.env.NODE_ENV參數判斷當前環境,確定當前需要轉發的接口服務器域名,並將相對路徑轉化爲絕對路徑,代碼如下:
/**
* 根據環境不同配置http請求的url域名
* @param url :請求的相對路徑
* @returns {string} :請求的絕對路徑
*/
export const getfulllUrl = (url) => {
let BASE_URL = '';
if (process.env.NODE_ENV === 'development') {
//開發環境 - 根據請求不同返回不同的BASE_URL
if (url.includes('/oilChainGasStation/')) {
BASE_URL = 'https://xxtest.tf.com';
} else if (url.includes('/passport')) {
BASE_URL = 'https://test.xxx.com';
}
} else {
// 生產環境
if (url.includes('/oilChainGasStation/')) {
BASE_URL = 'https://xx.tf.com';
} else if (url.includes('/passport')) {
BASE_URL = 'https://www.xxx.com';
}
}
return BASE_URL + url;
};
其中/oilChainGasStation/、/passport即爲接口關鍵字,https://xxtest.tf.com與https://xx.tf.com即爲對應關鍵轉發對應環境下接口的域名服務器,根據自己的項目情況做更改即可。最後,通過BASE_URL + url將相對路徑轉化爲絕對路徑,即爲接口請求的全路徑。
3.2、request.js
request.js是對發送http請求接口的封裝,其中Taro.request支持primise的使用。request的核心代碼如下:
import interceptors from './interceptors';
interceptors.forEach(i => Taro.addInterceptor(i));
// 添加請求的攔截器
Taro.addInterceptor(Taro.interceptors.timeoutInterceptor);
......
/**
* 根據環境不同配置http請求的url域名
* @param url :請求的相對路徑
* @returns {string} :請求的絕對路徑
*/
export function request(url, options) {
//url根據環境參數配置域名;拼接appStoken參數
url = stitchUrlParam(getfulllUrl(url), parseParamStr({ app_stoken: getAppStoken() }));
options = Object.assign({}, defaultOpts, options);
//添加自定義頭
const header = Object.assign({}, options.headers);
const requestOptions = {
url,
header,
data: options.body,
method: (options.method ).toUpperCase(),
};
return Taro.request(requestOptions);
}
interceptors也即第三部分要闡述的攔截器。可以使用攔截器在請求發出前或發出後做一些額外操作。
request即爲封裝的網絡請求方法。首先,對代碼的網絡請求的相對路徑的url做處理變爲絕對路徑,並拼接當前登錄的app_stoken(不同的項目視具體情況而定);其次對用戶自定義的header、methods做處理,默認header和method存放在defaultOpts變量中;最後,調用Taro.request方法發起請求;
實際開發中,修改header參數的情況並不多,最常見的POST請求下修改content-type爲form表單提交或者json格式提交。針對上述情況,基於request方法分別封裝了requestPost、requestPostJson、requestGet,代碼如下:
//POST 請求的http接口 ,默認表單提交
export function requestPost(url, params = {}, type = 'form') {}
//POST 請求的http接口 json方式提交
export function requestPostJson(url, params = {},) {}
// GET 請求的http接口
export function requestGet(url, params = {}) {}
上述三個方法與request稍有不同,request的參數option是一個對象,包含header部分和body部分(請求參數),而上述三個方法,header部分已經在方法內部實現,調用該方法時,只需關注接口url和接口參數。當然如果上述三個方法不能滿足你的開發需求,可自行在request的基礎的上封裝,或者直接調用request方法。
3.3、interceptors.js
在上文中提到過interceptors提供了Taro.request的攔截器。官方解釋如下:
在調用 Taro.request 發起請求之前,調用 Taro.addInterceptor 方法爲請求添加攔截器,攔截器的調用順序遵循洋蔥模型。
攔截器是一個函數,接受 chain 對象作爲參數。chain 對象中含有 requestParmas 屬性,代表請求參數。攔截器內最後需要調用 chain.proceed(requestParams) 以調用下一個攔截器或發起請求。
Taro 提供了兩個內置攔截器 logInterceptor 與 timeoutInterceptor,分別用於打印請求的相關信息和在請求超時時拋出錯誤。
核心代碼如下。其中statusCode可用於判斷網絡請求,此處只針對404、503做錯誤處理,如不滿足需求,可自行根據項目特點添加。statusCode爲200時,表示網路請求正常響應,對於具體業務而言,code爲0或者200才表示業務接口請求正常,code值根據各自項目特點可配置在specialCode中。對於請求正常的接口,返回data,異常接口拋出Toast提示,並返回Promise.reject,如需對異常請求做額外處理,可在對於接口請求處,通過catch捕獲異常。
function customInterceptor(chain) {
const requestParams = chain.requestParams;
return chain.proceed(requestParams).then(res => {
const { statusCode, data } = res;
const { code, msg, result } = data || {}; // "result" 字段兼容會員老接口
// 404
if (statusCode === HTTP_STATUS.NOT_FOUND) {
_handelErrorByCode('接口請求404,請檢查請求url');
return Promise.reject(res);
}
// 503
if (statusCode === HTTP_STATUS.SERVICE_UNAVAILABLE) {
_handelErrorByCode('接口請求503, 服務不可訪問');
return Promise.reject(res);
}
// 200
if (statusCode === HTTP_STATUS.SUCCESS) {
// 特殊碼處理了
if ((specialCode.includes(code)) || result === 'success') {
return data;
} else {
_handelErrorByCode(msg, data);
return Promise.reject(data);
}
}
});
}
攔截器提供了對Taro.request請求的處理,針對上一部分中request請求章節,對於header和mothod的處理,也可以通過攔截器實現。
4、踩坑記錄
在使用Taro、Taro-ui過程中,遇到了一些坑,記錄下:
1、key :在列表渲染時,如果確定已經使用了唯一的值作爲key,但是在微信小程序開發工具上運行是,仍舊有waring時,可強制轉換key爲字符串:String(item
.id);
2、自定義組件的className:自定義組件如果需要通過props傳遞className到組件中。需要在組件中額外聲明externalClasses(變量名不可變),其中my-class可以自行命名,在父組件中,可通過my-class屬性傳遞class,my-class只能是 短橫線命名法 (kebab-case),而不是 React 慣用的 駝峯命名法 (camelCase)。
export default class CustomComp extends Component {
static externalClasses = ['my-class']
render () {
return <View className="my-class">這段文本的顏色由組件外的 class 決定</View>
}
}
export default class MyPage extends Component {
render () {
return <CustomComp my-class="red-text" />
}
}
3、checkbox樣式修改方法:可以通過Label組件包裹checkbox組件,並影藏checkbox,同時自定義選中時的樣式,具體的實現可以https://developers.weixin.qq.com/miniprogram/dev/component/label.html。