由京东凹凸团队开源的小程序框架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
├── 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文件。
这部分重点介绍下lib下的envUtil、request和interceptors。
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将相对路径转化为绝对路径,即为接口请求的全路径。
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方法。
在上文中提到过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的处理,也可以通过拦截器实现。
在使用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。