背景
最近接到多端开发,因为老项目使用的React,考虑到迁移成本,选择了Taro,迁移成本相对较低,且上手较快。
Taro和uni-app
我做了一下调研,目前市面上优秀且成熟的开源框架有很多。其中,Taro和uni-app作为两大“豪门”框架,优秀之处各有千秋,为我提供了更多的选择项。
关于它们的对比可以参看下面这篇掘金好文:
- Taro vs uni-app选型对比经历
Taro
综合考量,尤其是前面提到的,迁移成本,我最后选择了Taro。下面主要介绍Taro的使用以及迁移中的功能总结。
Taro的官方文档内容很全面,基本的操作跟着官方文档即可完成,官方文档地址。
我的项目目前只有两个端的业务场景,分别是微信小程序和H5,所以技术探索也主要针对这两个端,文章也主要是这两个端使用总结。
新建项目
CLI 工具安装
代码语言:javascript复制# 使用 npm 安装 CLI
$ npm install -g @tarojs/cli
# OR 使用 yarn 安装 CLI
$ yarn global add @tarojs/cli
# OR 安装了 cnpm,使用 cnpm 安装 CLI
$ cnpm install -g @tarojs/cli
项目初始化
使用命令创建模板项目
代码语言:javascript复制$ taro init myApp
npm 5.2 也可在不全局安装的情况下使用 npx 创建模板项目
代码语言:javascript复制$ npx @tarojs/cli init myApp
安装依赖
代码语言:javascript复制# 进入项目根目录
$ cd myApp
# 使用 yarn 安装依赖
$ yarn
# OR 使用 cnpm 安装依赖
$ cnpm install
# OR 使用 npm 安装依赖
$ npm install
安装完成之后,文件结构已经生成如下图:
编译命令
代码语言:javascript复制// 微信小程序
# yarn
$ yarn dev:weapp
$ yarn build:weapp
# npm script
$ npm run dev:weapp
$ npm run build:weapp
// H5
# yarn
$ yarn dev:h5
$ yarn build:h5
# npm script
$ npm run dev:h5
$ npm run build:h5
项目迁移之React框架版
选择框架
因为我平时使用React框架进行开发,所以迁移的时候也直接选择了React框架。这个选择是在项目初始化的时候选择的,如下图:
项目迁移
直接把文件拷贝过来,然后进行调整,主要调整的内容有以下几个部分
UI框架的调整
原来的项目使用的是antd-mobile,迁移之后改成了@antmjs/vantui,部分组件名以及组件的用法略有不同。
比如下面的页
- antd-mobile中的List组件在@antmjs/vantui是没有的,所以需要重写这部分代码;
- Button组件两个UI都有,但是里面的属性存在差异,针对这部分差异进行修改即可;
// import { List, Button } from 'antd-mobile';
import { Button } from '@antmjs/vantui';
// antd-mobile的List组件使用
const listContent = content => {
return (
<div className='content-list'>
<List>
{content.list.map(item => {
return (
<List.Item className={classnames({ 'list-item-baseline': item.btnName })} prefix={materiaIcon[item.type]} key={item.type} description={item.btnName ? btnConten(item) : null}>
<div className={classnames({ required: item.required })}>{item.name}</div>
</List.Item>
);
})}
</List>
</div>
);
};
// @antmjs/vantui对List组件的改造
const listContent = content => {
return (
<View className='content-list'>
{content.list.map(item => {
return (
<View key={item.type} className='content-item'>
<View className='flex'>
<Icon color='#007af5' name={materiaIcon[item.type]} size='20px' />
<View className={classnames('ml5', { required: item.required })}>{item.name}</View>
</View>
{item.btnName ? btnContent(item) : null}
</View>
);
})}
</View>
);
};
// antd-mobile的Button组件使用
<Button color='primary' block shape='rectangular'>
咨询客服
</Button>
// antd-mobile的Button组件使用
<Button type="primary" block={true}>
咨询客服
</Button>
页面跳转方法
原生H5的跳转使用的是History对象提供的push或者replace方法,在Taro里使用Taro提供的路由API,因为小程序中tabBar中的页面和其他页面的跳转方法不一样,这个区别Taro也做了区分,为此我写了一个公共方法做跳转的统一处理。
代码语言:javascript复制/**
* 跳转处理
* @param {string} path app.config.js中的完整地址 例如/pages/home/index
*/
commonNavigateTo(path) {
/** @name 底导航路由列表 */
const switchList = ['/pages/home/index'];
if (switchList.includes(path)) {
Taro.switchTab({
url: path,
});
} else {
Taro.navigateTo({
url: path,
});
}
}
HTML标签
Taro v3.3以前是不支持使用HTML标签的,使用的是Taro提供的View、Text等标签,这些在Taro的组件库中有详细介绍。
代码语言:javascript复制import React, { Component } from 'react'
import { View, Text } from '@tarojs/components'
export default class C extends Component {
render () {
return (
<View className='c'>
<Text>c component</Text>
</View>
)
}
}
Taro v3.3 开始支持使用HTML标签,需要进行插件配置。
配置插件
1.首先下载安装插件 @tarojs/plugin-html
yarn add @tarojs/plugin-html
2.然后在项目配置中添加使用插件
代码语言:javascript复制// config/index.js
config = {
// ...
plugins: ['@tarojs/plugin-html']
}
注:
- 如果遇到不支持的标签可以使用Taro提供的组件,详见Taro组件库。
项目迁移之原生小程序
后面有规划把原生小程序项目也使用Taro开发成多端,但是目前还没有实际应用,待我实际应用之后,再进行更新,我预测会遇到一些有趣的问题。
开发“指南针”
开发过程中难免会遇到各种问题,不过它也侧面成为了我的“试金石”,我把遇到的问题、解决方案,详细的列出来,供jym参考,有些解决方案可能不是最优,欢迎大佬提供更优的方案。
路由跳转路径问题
Taro提供的路由跳转方法基本和小程序一致,可以参看文档。
这里着重强调一下跳转的url的完整性,即url要以右斜线开始,否则或被当做相对路由处理。
错误示例
代码语言:javascript复制Taro.navigateTo({
url: 'pages/order/index',
});
报错如下
errMsg: "navigateTo:fail page "pages/order/orderStatement/pages/order/index" is not found"
正确示例
代码语言:javascript复制Taro.navigateTo({
url: '/pages/order/index',
});
小程序文件过大的处理方法
1.根据提示开启压缩
Tips: 预览模式生成的文件较大,设置 NODE_ENV 为 production 可以开启压缩。 Example: $ NODE_ENV=production taro build --type weapp --watch
2.分包
分包限制如下,官方文档
- 整个小程序所有分包大小不超过 20M
- 单个分包/主包大小不能超过 2M
因为我们的项目不是很大,所以并没有实际应用这种方案,但是我再掘金上找了几个不错的参考文章:
小程序分包(Taro分包案例)
京东购物小程序 | Taro3 项目分包实践
3.vendors.js过大的原因
vendors.js文件中包含了node_modules 除 Taro 外的公共依赖,可能会因为某些原因导致它体积过大。
下面主要列一下我的项目中导致vendors.js文件过大的原因
3.1 引入了crypto-js
这个第三方加密库,会导致一些意外的内容被打包进去(具体是什么官方也没有说的特别明白,可能是node的一些依赖之类的),解决方案就是降低crypto-js的版本或者直接把crypto-js-min放进本地(本地83KB)。
Taro.request在H5端不能自定义header的解决方案
因为我的项目某些特殊业务逻辑,所以必须添加自定义header,但是H5端Taro.request不支持自定义header(小程序端支持),所以根据不同端区分request的引入。
代码语言:javascript复制/**
* @description 异步请求分发 H5和小程序使用的不一样
*/
import $ from './request-weapp';
import $2 from './request-h5';
export function requestFunc() {
if (process.env.TARO_ENV === 'weapp') {
return $;
} else {
return $2;
}
}
老项目迁移时H5端自定义路由
因为老项目有一些已经对外的入口,比如外推链接、第三方入口等,所以迁移的时候要进行兼容处理。
1.处理前的页面路由
处理前的页面路由如下,Taro框架自动生成的,显然和老项目的不一致
https://{{domain}}/pages/index/index(browser 模式)
2.自定义页面路由方案1
方案1 是使用Taro提供的方式,配置h5.router.customRoutes
代码语言:javascript复制config/index.js
module.exports = {
// ...
h5: {
// ...
router: {
customRoutes: {
// "页面路径": "自定义路由"
'/pages/index/index': '/index',
'/pages/detail/index': ['/detail'] // 可以通过数组为页面配置多个自定义路由
}
}
}
}
优点:不用做过多的逻辑
缺点:1)当需要配置的页面多的时候容易漏;
2)新增页面如果忘记补充,可能会导致跳转找不到页面
3.自定义页面路由方案2
自己编写自定义页面路由的代码逻辑
app.js
代码语言:javascript复制import resetRouter from '@utils/resetRouter';
// 挂载生命周期时,h5端引入路由重定向的处理方法
componentDidMount() {
// =>true:只有H5才处理路由
if (process.env.TARO_ENV === 'h5') {
resetRouter.resetRouter();
}
}
utils/resetRouter.js
/**
* @description 路由拦截处理 页面404重定向、页面非Taro最终的H5路由重定向等
*/
import Taro, { Current } from '@tarojs/taro';
/**
* 获取小程序tabBar的pagePath数组对象
* @param {Object} taroAppConfigInit 项目配置项
* @return {Array} 最终得到的pagePath数组
*/
const getTabBarPageList = taroAppConfigInit => {
const tabBarInit = taroAppConfigInit.tabBar ? taroAppConfigInit.tabBar : {};
const tabBarList = tabBarInit.list ? tabBarInit.list : [];
const tabBarPageList = [];
if (tabBarList.length !== 0) {
tabBarList.forEach(item => {
tabBarPageList.push('/' item.pagePath);
});
}
return tabBarPageList;
};
/**
* 获取全部路由的数组对象
* @param {Object} taroAppConfigInit 项目配置项
* @return {Array} 最终得到的数组数组
*/
const getAllPagesList = taroAppConfigInit => {
const allPagesListInit = taroAppConfigInit.pages ? taroAppConfigInit.pages : [];
let allPagesList = [];
allPagesListInit.forEach(item => {
allPagesList.push('/' item);
});
return allPagesList;
};
/**
* 获取页面是否是404的布尔值
* @param {Array} allPagesList 全部路由数组
* @param {string} pathname 当前页面路由
* @return {boolean} 最终得到的布尔值
*/
const getNoFoundFlag = (allPagesList, pathname) => {
let flag = true;
if (allPagesList.includes(pathname)) {
flag = false;
}
return flag;
};
/**
* 页面路由处理 如果不包含pages的重组成/pages/pathname/index的格式
* @param {string} pathname 当前页面路由
* @return {boolean} 重组后的路由
*/
const resetPathname = pathname => {
let pathnameInit = '';
if (pathname.indexOf('pages') === -1) {
pathnameInit = '/pages' pathname '/index';
} else {
pathnameInit = pathname;
}
return pathnameInit;
};
/**
* 跳转处理 跳转到tabBar页面使用switchTab,其他使用navigateTo
* @param {string} pathname 当前页面路由
* @param {string} search 当前页面路由参数
*/
const switchTabOrnavigateTo = (pathname, search) => {
// 隐藏配置window.__taroAppConfig(包含app.config.js中所有内容)
const taroAppConfigInit = Current.app.config ? Current.app.config : {};
const allPagesList = getAllPagesList(taroAppConfigInit);
const tabBarPageList = getTabBarPageList(taroAppConfigInit);
const pathnameInit = resetPathname(pathname);
console.log(pathnameInit, 'pathnameInit');
//=>true: 如果路由类型是tabBar
if (tabBarPageList.includes(pathnameInit)) {
return Taro.switchTab({
url: pathnameInit,
});
} else {
const noFoundFlag = getNoFoundFlag(allPagesList, pathnameInit);
const url = noFoundFlag ? '/pages/dispatch/nofound/index' : pathnameInit search;
return Taro.navigateTo({
url: url,
});
}
};
/**
* 路由跳转拦截处理
*/
const resetRouter = () => {
// H5先从location获取路由getCurrentInstance().page值为nullTaro还没有修复
let { pathname, search } = window.location;
switchTabOrnavigateTo(pathname, search);
};
export default { resetRouter };
缺点:需要自己写逻辑,增加了额外的工作量
优点:一次性处理了所有的页面路由,不需要再次添加,且增加了404的重定向
UI框架
我们配合Taro的UI框架最终选择了有赞的@antmjs/vantui。这个UI框架提供的组件很丰富,常见的功能都覆盖到了,不过它的api文档写的略微简单,我后面可能写一篇它的使用总结。
总结
万事开头难,但是世上无难事只怕有心人,对于兼容性问题,考虑到框架底层的代码我无法修改,但是可以通过环境区分做处理,虽然繁琐了一点,但是可以帮助解决遇到的问题。