1端开发6端复用:去哪儿RN转QTaro实战经验分享

原创 刘亮亮 2026-01-08 18:02 北京

文章概览

  • 项目背景及目标

  • 技术选型

  • 项目筹备

  • 迁移实战

  • 项目复盘

  • 结语

保障数据情况

作者介绍:

刘亮亮,2019年加入去哪儿旅行火车票团队,火车票前端技术委员会成员,主要负责火车票/汽车/船票营销和辅营相关业务开发。

随着公司业务持续拓展,多端实现相同功能的需求日益凸显。与此同时,多端独立开发的弊端逐渐显现:

1. 人力成本上升,开发人员需掌握多种技术栈,学习成本高、负担重;

2. 维护成本增加,问题修复与迭代升级周期延长,效率降低。

为解决上述问题,火车票大前端团队启动“统一前端技术栈”项目,旨在通过一套代码实现多端复用。

项目目标:

1. 实现一套React Native(RN)代码在iOS、Android、Harmony、支付宝小程序、微信小程序及H5共6端复用;

2. 转换工具的代码复用度>80%

1. 考量因素

  • 当前火车票前端需求以RN开发为主,需将RN项目高效转化为多端应用,提升产品体验一致性。

  • QTaro是大前端中心基础框架团队基于Taro打造的多端解决方案,支持小程序、H5与RN项目同步开发。其在原生Taro生态基础上,提供了公司内部多端开发工具、业务扩展库及开发规范,能有效降低业务前端开发成本。QTaro还提供RN代码转换、运行时支持及项目发布能力,与火车票项目诉求高度契合。

  • 尽可能保留原有RN开发习惯,降低团队学习成本。

整体方案:依托QTaro的跨平台能力,联合基础框架团队打通QTaro与QRN,基于QTaro实现业务跨端落地,最终将QRN运行于多端,从而降低学习成本、提升开发效率、减少人力投入。

2. 方案挑战

  • 组件与属性差异大:涉及RN/QRN/第三方库组件与API约82处,CSS 属性差异30余处,如何抹平差异实现无缝衔接是关键。

  • 业务逻辑层多端差异:页面路由、登录、支付等功能需各端独立实现。

  • 多端性能优化:转换后代码在各端的性能表现需持续优化。

  • 请求接口统一:如何实现各端网络请求接口的统一封装。

项目分两期推进:

  • 一期目标:完成订单详情页从RN到QTaro的转换,并上线微信小程序版本。选取该页是因为其UI组件丰富、交互相对简单,适合作为技术验证切入点。

  • 二期目标:完成火车票所有RN项目的转换并全端上线。

1.转换工具设计

1.1 整体流程

1.1.1 方案选型

  • 方案一: RN转多端。以RN项目为开发起点,隐藏中间转换项目,保持RN开发习惯,实现RN→多端单向转换,学习与开发成本最低。

    • RN转换成QTaro中间项目

    • 中间项目转多端(WX小程序/H5/ALI小程序)的方式,是RN单向转换,最小的开发成本就可实现RN代码一拖六

  • 方案二: QTaro转多端。以QTaro中间项目为起点,实现QTaro→6端转换,转换工具实现复杂,且需遵循RN样式约束,开发成本较高。

    综合评估后,选择方案一:RN转多端。兼顾开发效率、开发成本、学习成本,以及减少对当前RN项目的影响。

    • 从QTaro中间项目作为开发起点,转RN。必须按照规范《 React Native 端开发前注意》开发,涉及6端的转换, 转换工具实现复杂;

    • 按照QTaro的RN规范开发,本质是以RN的样式管理约束。在样式上统一多端,木桶效应,短板对齐(向最弱的RN对齐);

    • 在灵活的QTaro项目下开发,还要施加RN的样式约束,不如直接以RN项目开发更顺畅;

    • QTaro->RN可能存在诸多问题,对当前RN项目影响大(线上出故障的概率变高)而且增加维护成本。

1.1.2 中间项目说明

  • 中间项目是临时&隐藏项目,  位置在当前项目.taroCache/project目录下。开发者不需要关心.taroCache,可以认为它不存在。

  • 中间项目符合QTaro框架规范的要求。

  • 开发者使用rn2QTaro cli命令行进行操作,内部调用Taro命令对中间项目进行构建。

  • 经过Taro build的处理之后,中间项目将会生成目标平台产物。

1.1.3 一套代码6端复用的思路

  • RN项目->转换工具->QTaro中间项目->ALI小程序/WX小程序/H5  3端

  • RN项目->Metro->IOS/Adr/Harmony  3端

1.2 转换工具流程图

转换流程说明:

1.2.1 初始化中间项目

1. rn2QTaro拉取模板项目。模板项目是最小化的QTaro Js项目,承载转换后的JS代码。

2. 根据当前项目中rn2QTaro.config.js配置项,安装中间项目的依赖文件以及RN多端库。

1.2.2 转换过程

1. 转换工具遍历项目所有JS文件,根据配置项,将RN/QRN等相关库映射为替换库。

2. 删除RN项目中所有propTypes。

1.3 RN库映射多端库

1.3.1 架构图说明

1. 转换工具根据配置文件,把项目中使用的RN库映射为对应的多端库。

2. 多端库通过QTaro的MultiPlatformPlugin引用对应平台实现的组件和API。

1.3.2 架构亮点

1. 多端库的代码结构设计,利用QTaro已有的多端能力(MultiPlatformPlugin)进行多平台切换,简单高效。

2. 通过封装QTaro抽象层的组件和API构建的RN等效多端库,可以随QTaro的扩展而动态支持更多平台。

1.4 抹平组件属性差异

1.4.1 方案选择

方案分析:

  • RN和其他端相同功能的组件,例如: View, Text等,  默认属性差异大, 且style属性多为动态赋值,用babel处理非常复杂。例如: 为CSS样式width属性添加'PX'单位。期望将width: 20转义为width: "20PX",但实际项目多是动态赋值:

  • babel转义难以完成。

  • 选用运行时方案,仅需封装各端的组件和API,修改接口或属性向RN对齐,即可完成。

  • 在RN项目中,多采用import进行引用,而import语句本身是静态的,在转义阶段就已确定,因此import路径的替换必须放在转义时进行。

方案确认:

1. 通过babel AST完成RN库映射为多端库。

2. 在运行时抹平各组件CSS样式差异,抹平样式模块封装在基类组件中。

1.4.2 行内样式 & 样式文件

RN使用JS对象来定义样式,并通过StyleSheet.create方法来创建样式表。

//styles.js
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5FCFF',
  }
});


//App.js
<Viewstyle={styles.container}>
</View>

当RN转QTaro,使用react开发时,同样支持使用JS对象定义样式。保持RN原有的CSS-in-JS样式是比较简单的处理方式。

另一个问题是react中没有StyleSheet.create方法。

解决方法2种:

1. babel AST转为

//styles.js
//babel转义: 把StyleSheet.create的入参,直接赋值给styles
const styles = {
  container: {
    flex: 1,
    backgroundColor: '#F5FCFF',
  }
};


//App.js
<Viewstyle={styles.container}>
</View>

2. 自己实现StyleSheet.create

const StyleSheet = {
    create(styles) {
        const result = {};
        Object.keys(styles).forEach(key => {
            result[key] = styles[key]
        });
        return result;
    }
};

1.4.3 行内样式style兼容

//RN中style支持样式数组,QTaro中不支持
<Viewstyle={[styles.container,extraStyle]}>
</View>

解决方法:

1. 自己实现StyleSheet.flatten,递归地展平数组中的每个样式对象,并将它们合并成一个单一的样式对象。

2. 样式处理

3. 方式1: babel 转义

//RN中style支持样式数组,QTaro中不支持
<Viewstyle={StyleSheet.flatten([styles.container,extraStyle])}>
</View>

方式2: 在兼容组件的入口,调用StyleSheet.flatten转为单一样式对象

1.4.4 更多样式问题

1. 尺寸单位 

RN中许多样式属性是不带单位的。因为RN的样式系统是基于弹性布局(Flexbox)设计的,它在不同的平台(iOS 和 Android)上会自动进行适配。 

但是在QTaro中,建议使用px、%作为尺寸单位,其默认会根据平台对单位进行转换,不希望做默认转换需要设置为"PX"。 

我们评估RN代码转QTaro,大多数需求场景设置"PX"较为合理,实际项目测试的结果也符合预期。 

常见的width, height, margin, bottom等,约有60多个。
可参考文档: 《Layout Props · React Native》

2. 阴影

//RN代码 shadow样式
StyleSheet.create({
    myView: {
        shadowRadius: 5,
        shadowOpacity: 0.5,
        shadowOffset: {x: 2, y: 2},
        shadowColor: 'gray'
    }
})
//转为 react boxShadow样式
StyleSheet.create({
    myView: {
        boxShadow: `2PX 2PX 5PX 0PX gray`
    }
})

3. 动画 

将RN的transform属性转换为Web的transform属性。 

rn支持的转换:

  1. rotate系列:rotate, rotateX, rotateY, rotateZ

  2. scale系列:scale, scaleX, scaleY,scaleZ

  3. translate系列:translate, translateX, translateY, translateZ

  4. skew系列:skew, skewX, skewY

//RN的 transform
{
        transform: [  
            { rotateZ: '-60deg' },  
            { translateY: -8 },  
            { translateY: 4 }  
        ]
}


//react的 transform
{  
     transform: 'rotateZ(-60deg) translateY(-8PX) translateY(4PX)'  
} 

4. 样式替换 

例如: RN的marginStart需要替换为marginLeft, marginHorizontal需要替换为marginLeft, marginRight。

//RN属性
{
        marginHorizontal: 10
}


//转换后代码
{
    marginLeft: 10PX,
    marginRight: 10PX
}

5. 默认样式差异

  • display:

    • RN: 组件的display属性是flex,所有的布局都是基于Flexbox。

    • CSS: 组件的display属性是block,类似于HTML中的div元素。

  • flex和flexDirection:

    • RN: 在View上默认display: flex和 column。子组件垂直排列

    • CSS: 如果设置为flex布局,默认值是row。子元素水平排列

  • box-sizing:

    • RN: 默认值为border-box且只支持这种

    • CSS: 默认值是content-box

  • 更多默认样式差异可参考:

2. 实现RN多端库

实现UI组件及API库,替换RN/QRN/第三方RN库中对应的UI组件及API。RN多端库是抹平多端差异的核心库,  具有通用性,适用于各RN项目。

2.1 已实现多端库

还包括qunar-react-native, react-native-keyboard-aware-scroll-view, react-native-render-html, react-native-WebView等共14个RN多端库, 不再详细列举。

RN标准库中组件和API实现已覆盖80%(因平台差异, 无法实现组件占位留空)。

火车票项目中组件和API覆盖86%,和平台关联较大的部分组件(截屏、QP包管理、日历、通知等)已被降级处理。

2.2 组件实例接口差异

RN中组件节点的实例方法与QTaro组件库存在差异 

常见的有:

  1. this.viewRef.current.measureLayout测量视图布局

  2. this.qVideoRef.current.play/pause/seek视频播放等操作

  3. this.textInputRef.current.blur/isFocused判断焦点操作

通过自定义组件实例的外部接口,确保多端库组件实例方法与RN组件接口一致。

下面以View组件为例,添加可供外部调用的方法measureLayout,和RN View接口保持一致。

3. 业务代码多端适配方案

RN转QTaro框架, 不仅要适配RN/QRN/三方RN组件,还需处理平台相关的业务逻辑差异。

深入分析每个差异功能后,发现业务逻辑差异是因使用RN原生插件引起,以NativeModules为切入点可以做到隐藏差异。

主要差异功能:登录、支付、页面路由、网络请求

以Network为例,说明业务代码的多端适配图

3.1 架构说明

以NativeModules为切入点,业务开发同学不需要关心平台差异,同时可以对NativeModules扩展,后续支持更多平台。

3.2 NativeModules多平台切换

@taro/NativeModules实现了一套和火车业务RN原生插件接口一致的类。

  • RN平台通过@taro/NativeModules/index.js引用react-native的 NativeModules

  • WX平台通过

    @taro/NativeModules/index.weapp.js引入weapp多端库实现。

以接口请求Network.js为例说明:

  • 多端适配前

//RN插件 NativeModules.TRNQunarNetwork 发送请求  
import { NativeModules } from 'react-native';
  • 多端适配后

//路径替换,引用多端文件  
import NativeModules from '@taro/NativeModules';
  • RN平台@taro/NativeModules/index.js

//RN平台直接导出 react-native 的 NativeModules。外部通过 NativeModules.TRNQunarNetwork 获取原生请求插件  
import { NativeModules } from 'react-native';  
export default NativeModules;
  • WX平台

    @taro/NativeModules/index.weapp.js

//微信小程序平台导出自定义 NativeModules, 对外接口和RN平台保持一致。外部通过 NativeModules.TRNQunarNetwork 获取WX平台请求接口  
const NativeModules = {    
  TRNQunarNetwork,//封装了WX端实现类  
}   
export default NativeModules; 

3.3 方案总结

该方案进一步封装了业务逻辑的多端差异,把NativeModules作为多平台切换的入口点,在各个平台封装一组与原生RN插件等效的功能类(例如 TRNQunarNetwork, TRNNavigator),并确保功能类对外接口在各平台完全一致。

可以完美和react-native的NativeModules数据结构契合,做到各端平滑切换。涉及业务代码的多平台差异被隐藏在NativeModules中,后续产品需求开发时,开发同学仅需聚焦到业务逻辑即可。

4. 项目的难点

4.1 RN转微信小程序严重超Size问题

4.1.1 背景

小程序总包剩余空间不足, 产品需求开发已严重受限。

RN转QTaro项目面临的最大难题是要解决Size问题,如果不能上线微信小程序,多端复用也就失去意义。

4.1.2 解决方案

把所有的RN页面利用QTaro框架转为H5,通过WebView方式加载。

WebView加载方式选择:

4.1.2.1 MPA面临的问题

1. 不能共享内存实例对象:  global上注册了很多功能类和数据类,包括共享的ABTest数据,QConfig等。这些数据在各H5页面间不同步。

2. 事件订阅和通知无效: 页面通过EventCenter注册事件订阅,监听某个页面的数据变化。页面无法监听其他页面触发的事件。

解决方法 : 难以修复。火车项目页面>100个,MPA下修复共享实例问题,需要改动的代码量巨大,将会严重阻塞项目进度。

4.1.2.2 SPA面临的问题

1. 多个RN项目是单独打包出多个H5产物。同一个H5产物,页面之间可以共享对象实例,但不同的H5产物之间仍无法共享数据。

解决方法:多个RN项目合并打包,打出一个H5产物。

4.1.2.3 MPA和SPA共同面临的问题

1. 修改WebView的hash方式传参,H5页面被重新reLaunch,页面栈丢失。

解决方法:同域名可以通过local storage传参,跨域下通过后端接口共享数据。

2. 小程序和H5登录态不同步问题

解决方法:外部登录态通过URL传入WebView,保存在WebView的cookie中。

3. 首次资源下载问题过大

解决方法:通过webpack文件分割优化后,首次下载资源1.7M,后续页面下载平均200K。

综合考虑以上因素,选择SPA方案能够更好地解决项目中的问题。SPA在内存对象共享、页面流畅度以及资源利用效率等方面具有优势,且能解决项目中面临的关键问题。

5.1 项目的成果

5.1.1 转换工具代码复用度87%

针对所有涉及的RN项目,分别获取起始和结束时的git commit hash(版本提交哈希值),然后进行git diff操作,计算得出两次提交之间代码行数的变化量与总代码行数的比值为13%。

粗略估计转换工具可以节省RN转换整体工作量的 75%。

5.1.2 一套代码多端复用

UI样式效果微信小程序H5对比Adr-RN:

UI样式和功能向RN端看齐, 减少用户多端切换时的学习成本,保证用户各端功能体验一致。

5.1.3 节约人力成本>50%

以3端为例:

  • 接入QTaro方案,节省(300%-160%)/300% =47%

  • 业务差异封装后,  节省(300%-100%)/300% =67%

  • 覆盖的端越多,每端成本越小

以业务线投入4人到非RN端开发计算,统一多端后,年化节约人力 4*235pd( 每人每年pd数 ) = 940pd。

5.1.4 突破小程序Size限制

通过加载单页面H5,解决了RN转WX小程序严重超Size的问题。

转H5后,车次列表页TTI-P90对比如下:

安卓手机,微信原生和微信H5对比

IOS手机,微信原生和微信H5对比

结论:

在IOS/Adr系统上,H5的TTI-P90基本优于微信小程序原生代码。

在解决微信小程序Size问题上,目前取得了阶段性的成功。火车票前端正在深入分析原因,尝试通过加载流程优化,资源预加载等策略进一步提升用户体验。

5.2 不足与展望

5.2.1 组件兼容性

例如: RN部分组件/API在平台兼容性方面存在问题,其中较为棘手的是Animated、PanResponder等与动画手势相关的组件。

H5多端库动画问题, 可以参考开源库 https://github.com/necolas/react-native-web进一步完善。

WX多端库动画问题:

  • WX动画是通过API createAnimation实现,  而RN的Animated由3-4个API配合使,考虑到调用方式以及动画与组件的关联,完全模拟较为复杂。

  • wx.createAnimation依赖于小程序的JS运行环境,受限于小程序的性能和资源分配,尤其是在低端设备上,容易出现卡顿。

解决思路:可以使用CSS动画实现。但CSS不能动态调整动画参数,不适合作为通用的多端库组件,因此需要在平衡动画流畅性和通行性上做取舍

5.2.2 转换工具覆盖度

多端库组件和API,目前主要适配火车RN的项目,未来可扩展支持更多RN生态组件。

5.2.3 样式边缘情况

组件适配的边缘情况的处理不够完善。在抹平组件属性差异的过程中,个别属性差异可能导致特定设备或浏览器上显示异常,需持续测试与完善。

本项目提供了一套将RN项目转换为QTaro框架的完整解决方案,涵盖转换工具、多端兼容库与业务差异封装,显著提升开发效率与多端一致性。在迁移过程中,团队积累了丰富的跨端适配经验,也深刻认识到协作与沟通在复杂项目中的关键作用。

未来,火车票前端将持续跟进QTaro框架演进,进一步优化性能与体验,为业务赋能提供更强支撑。

阅读原文

跳转微信打开