背景
Taro 是一套遵循 React 语法规范的 多端开发 解决方案。现如今市面上端的形态多种多样,Web、React-Native、微信小程序等各种端大行其道,当业务同时在不同的端都要求有所表现的时候,针对不同的端编写多套代码的成本显然非常高,这时只编写一套代码就能适配到多端的能力就显得极为重要。
使用 Taro,我们只需书写一套代码,再通过 Taro 的编译工具,即可将源代码分别编译出在不同端(微信/百度/支付宝/字节跳动小程序、快应用、H5、React-Native 等)运行的代码。
58租房-创新找房业务有上线三端并高度一致的需求,为了节约开发人力和创新,我们在该项目中探索性的尝试使用Taro进行开发,下面将从 Taro框架编译原理、源码改造、依赖改造、业务开发实践过程几个角度来分别阐述一下。
Taro框架编译原理
因为H5和React-Native都可以遵循React语法规范开发,在 React 中,是使用 JSX 来作为组件的模板的,而小程序则与 Vue 一样,是使用字符串模板的。这样两者之间就有着巨大的差异了。
JSX
render () {
return (
<View className='index'>
{this.state.list.map((item, idx) => (
<View key={idx}>{item}</View>
))}
<Button onClick={this.jump}>跳转</Button>
</View>
)
}
小程序模板
<view class="index">
<view wx:key={idx} wx:for="{{list}}" wx:for-item="item" wx:for-index="idx">{{item}}</view>
<view bindtap="jump">跳转</view>
</view>
那么这个时候我们就想,要是能够将 JSX 编译成小程序模板就好了。
事实上在我们平时的开发中,这种编译的操作到处可见,babel 就是我们最常用的 JS 代码编译器,一般浏览器是不能支持一些非常新的语法特性的,但我们又想使用它们,这个时候就可以借助 babel 来将我们的高版本的 ES 代码,编译成浏览器可以运行的 ES 代码。而我们像要将 JSX编译成小程序模板,也是同样的道理。我们首先来了解一下 Babel 的运行机制。
Babel 作为一个 代码编译器 ,能够将 ES6/7/8 的代码编译成 ES5 的代码,其核心利用的就是计算中非常基础的编译原理知识,将输入语言代码,通过编译器执行,输出目标语言的代码。编译原理的一般过程就是,输入源程序,经过词法分析、语法分析,构造出语法树,再经过语义分析,理解程序正确与否,再对语法树做出需要的操作与优化,最终生成目标代码。
将 JSX 编译成小程序模板,非常幸运的是 babel 的核心编译器 babylon 是支持对 JSX 语法的解析的,我们可以直接利用它来帮我们构造 AST,而我们需要专注的核心就是如何对 AST 进行转换操作,得出我们需要的新 AST,再将新 AST 进行递归遍历,生成小程序的模板。
源码改造
我们是基于Taro1.3.10版本开发的,为了使Taro兼容58RN工程我们对源码做了以下改造:
taro脚手架taro-cli
- 升级内部react-native版本"react-native": "0.57.8"
- 固定注册包名
AppRegistry.registerComponent
为wuba
taro依赖库taro-rn
1.删除Expo相关依赖的api,保证依赖纯净
taro依赖库taro-router-rn
修改initRouter
中基于react-navigation
的初始化参数,使其保证和58RN跳转方式统一
为了便于版本管理,我们将taro关键字转换为fangchan-taro,并修改其依赖树,fangchan-taro托管于npm,后续陆续改造了taro-cli、taro-rn、taro-rn-component等依赖,我们把Taro下的依赖统一进行管理,置于同一个组织,标记为公开,形如:
@fangchan/taro-rn、@fangchan/taro-cli
图上是Taro开发RN端的工作流程,通过执行taro build --type rn --watch
指令将Taro编译成RN代码,并开启metro server将rn_temp
下的js文件打包成js bundle
,通过npm start
开启服务后就可以在58rn测试载体页上愉快的访问了。
依赖改造
Taro提供的组件和Api相对比较基础,通用性更强,但是58移动端并没有对应的sdk支持,所以我们暂时去掉了不支持的api,并在有必要的情况下进行重写。
例如taro-rn中的请求Request
是必要的API,我们对其改造,让它内部调用房产SDK的请求,保证请求内部流程一致。
import HMS from 'house-middleware-sdk'
function request (options) {
options = options || {}
let url = options.url
let data = options.data || {}
if (typeof options === 'string') {
options = {
url: options
}
}
let method = options.method || 'GET'
method = method.toUpperCase()
if (method === 'GET') {
return HMS.get(url, data)
}
if (method === 'POST') {
const formData = new FormData()
Object.keys(data).forEach(key => {
formData.append(key, data[key])
})
return HMS.post(url, formData)
}
}
export default {
request
}
房产业务中使用到的业务组件如筛选、底部栏等,不同端提供的Api如登录、认证等需要我们自行开发,我们把开发分为两个阶段:
- 第一阶段为了业务快速迁移上线,尽量复用各端已有组件,使用条件编译完成各端打包
- 第二阶段对于通用性较强的组件、基于Taro封装各端API和组件库提高开发效率、降低维护成本
目前我们处于第一阶段,使用Taro提供的条件编译来处理不同端的差异
if (process.env.TARO_ENV === 'weapp') {
// 微信小程序端执行逻辑
} else if (process.env.TARO_ENV === 'h5') {
// h5 端执行逻辑
} else if (process.env.TARO_ENV === 'rn') {
// react-native 端执行逻辑
}
业务开发实现
1.项目初始化
我们通过改造的cli来快速构建项目fangchan-taro init TaroDemo
,构建好后项目目录结构如下
├── dist 编译结果目录
├── config 配置目录
| ├── dev.js 开发时配置
| ├── index.js 默认配置
| └── prod.js 打包时配置
├── src 源码目录
| ├── pages 页面文件目录
| | ├── index index 页面目录
| | | ├── index.js index 页面逻辑
| | | └── index.css index 页面样式
| ├── app.css 项目总通用样式
| └── app.js 项目入口文件
└── package.json
我们首先需要修改入口文件,在构造中初始化全局参数,在全局配置config
中定义页面以及设置小程序导航栏和设置rn特殊返回键处理等操作。
class App extends Component {
constructor(props) {
super(props);
initGlobalConst();
}
config = {
pages: [
'pages/index/index',
'pages/listpage/index',
'pages/filterpage/index',
'pages/guidepage/index',
'pages/demandpage/index',
],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'WeChat',
navigationBarTextStyle: 'black',
},
backStackConfig: {
ctrlFun: process.env.TARO_ENV === 'rn' ?
require('./utils/initRN').ctrlBackStackFun : false
}
};
render() {
return (
<Index/>
)
}
}
Taro.render(<App/>, document.getElementById('app'));
然后在'src/index/index.js'中的componentWillMount
生命周期处理多端的初始化操作
componentWillMount() {
process.env.TARO_ENV === 'rn' && initRN((goal) => {
this.renderPage();
});
process.env.TARO_ENV === 'h5' && initH5((goal) => {
this.renderPage();
});
process.env.TARO_ENV === 'weapp' && initH5((goal) => {
this.renderPage();
});
}
以RN端为例,需要在initRN
方法中从native端获取header信息、跳转协议等,所以这一步是必不可少的。
function initRN(callback) {
HMS.initPackage(
() => HMS.initNativeParams(
() => {
const cacheFlag = global.jumpParams.content.params.useCache || true;
HMS.CacheUtil.setUseCache(cacheFlag);
upDateGlobalConst('PACKAGE', global.CURRENT_PACKAGE);
upDateGlobalConst('FULL_PATH', global.jumpParams.content.params.full_path);
handleTargetPageType(callback);
}
)
);
}
2. 页面间跳转
我们只需要在入口文件的 config 配置中指定好 pages,然后就可以在代码中通过 Taro 提供的 API 来跳转到目的页面,例如:
// 跳转到目的页面,打开新页面
Taro.navigateTo({
url: '/pages/page/path/name'
})
// 传入参数 id=2&type=test
Taro.navigateTo({
url: '/pages/page/path/name?id=2&type=test'
})
3. 状态管理
为了减少学习成本,我们沿用RN端使用的状态管理机制mobx进行状态管理,在Taro端使用方式和RN端完全一致。这样也方便已有的RN项目后面能快速迁移到Taro。
4.样式管理
样式管理是多端开发的一大挑战,因为 React Native 与一般 Web 样式支持度差异较大。样式上 H5 最为灵活,小程序次之,RN 最弱,统一多端样式即是对齐短板,也就是要以 RN 的约束来管理样式,同时兼顾小程序的限制。
不过这也正巧适用于我们团队,因为我们对RN样式控制比较熟,所以我们并没有采用scss的方式,而是沿用编写RN样式的方式,完全使用style来编写Taro样式。
<View
style={{
display: 'flex',
position:'relative',
width: Taro.pxTransform((global.WINDOW_WIDTH_VALUE - 30) * 2),
height: Taro.pxTransform(130)
flexDirection: 'column',
backgroundColor: '#EAEAEA'
}}
/>
其中有几点特殊需要注意:
1.因为RN一般需通过Dimensions 获取宽高再进行换算,Taro提供的 pxTransform() 可解决该问题,但编译 RN 端样式文件时并没有考虑这点,
所有数字必须在style中用Taro.pxTransform()包装,且单位是px。
- 在开发中必须声明flex布局和排版,因为RN和小程序H5默认横纵不一样。
- position: 'absolute'需要在外层父view设置position: 'relative'。
- 覆盖组件样式可以通过style传递,但style不支持数组。
最终看一下实现的效果还是能保证高度统一的。
开发中遇到的问题
1.语法问题
由于微信小程序端的限制,有一些jsx用法不能得到很好地支持,比如不能使用 Array#map 之外的方法操作 JSX 数组、暂不支持在 render() 之外的方法定义 JSX、不能在 JSX 参数中使用对象展开符、不支持无状态组件等。
2.房产组件库依赖问题
因为组件库中使用了es6语法,且部分组件有mobx的引用,直接依赖会有不兼容的问题,所以将其统一用babel转成es5格式打包到lib中引用。
3.跨域问题
RN不存在跨域问题,小程序网络请求需要在小程序设置中配置,H5存在跨域的问题,这个可通过 devServer.proxy 解决,以及编译打包的静态资源是固定文件名,建议改成带 hash 值方便缓存管理,这些配置在项目里的 src/config 中都能找到。
总结
本文从一个RN开发者角度来进行Taro跨平台项目实践,其中在状态管理及样式管理上都采用了和RN一样的机制,一是为了组内同学可以快速上手开发,二是为了将已有rn项目能快速平移到Taro。但是对H5和小程序两端掌握的能力有限,需要实践积累。
目前由于组件库、sdk积累较少,前期开发业务的同时要同时进行业务开发、底层封装、脚手架调整,业务开发速度因此受到影响,粗略的估算是RN同等业务开发时间的2倍左右。预计未来理想情况下,基于完整的组件库、sdk、以及标准化的协议可以抹平系统、平台、端之间的大多数差异,开发效率趋近于目前RN情况。
小程序各端统一技术栈到Taro后,两方理论上可以做到无缝衔接,底层资源共享、复用,这将显著提升开发效率,后续我们在推动四端逻辑、协议统一的同时,会加强组间沟通,与FE同学共同促进通用层优化,使业务开发者可以专注于业务本身。