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

本文详述了去哪儿火车票团队将现有 React Native (RN) 项目迁移至基于 Taro 的企业级多端解决方案 QTaro 的实践经验。项目旨在通过一套代码实现 iOS、Android、HarmonyOS、支付宝小程序、微信小程序及 H5 等六端的复用,以降低开发和维护成本。文章深入探讨了技术选型、面临的组件和逻辑差异挑战,并详细介绍了转换工具的设计思路、RN 库映射多端库、组件属性差异抹平、业务代码多端适配等核心技术方案。特别针对微信小程序 Size 超限问题,团队采用了将 RN 页面转为 H5 并在 WebView 中加载的方案,并对比了 MPA 和 SPA 的优劣,最终选择了 SPA 实现。项目成果显著,包括代码复用率达到 87%、节约人力成本超过 50%,并成功突破小程序体积限制,且 H5 页面的 TTI-P90 性能表现优于小程序原生。文章最后也坦诚了当前在组件兼容性、转换工具覆盖度及样式边缘情况上存在的不足与未来展望。





e7aea0b2-cb9f-4322-b3ce-66271860cbe5.png

文章概览

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

  • 项目背景及目标

  • 技术选型

  • 项目筹备

  • 迁移实战

  • 项目复盘

  • 结语

保障数据情况

作者介绍:

刘亮亮,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 端开发 6 端复用:去哪儿 RN 转 QTaro 实战经验分享

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

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,可以认为它不存在。

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

  • 中间项目符合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 端开发 6 端复用:去哪儿 RN 转 QTaro 实战经验分享

转换流程说明:

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 端开发 6 端复用:去哪儿 RN 转 QTaro 实战经验分享

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 方案选择

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

方案分析:

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

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

  • 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: {
        flex1,
        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: {
          flex1,
          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: {
                      shadowRadius5,
                      shadowOpacity0.5,
                      shadowOffset: {x2, y2},
                      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属性
                  {
                          marginHorizontal10
                  }


                  //转换后代码
                  {
                      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 已实现多端库

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

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

                  还包括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接口保持一致。

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

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

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

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

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

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

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

                  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加载方式选择:

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

                          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:

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

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

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

                            5.1.3 节约人力成本>50%

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

                            以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对比如下:

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

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

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

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

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

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

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

                            结论:

                            在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框架演进,进一步优化性能与体验,为业务赋能提供更强支撑。


                            AI 前线

                            22 美元 AI 便签走红 CES 2026!这家中国公司让外媒狂追,24 小时曝光近 10 亿

                            2026-1-10 18:18:35

                            AI 前线

                            被迫裁员 75%、只剩 6 个月续命!Tailwind 创始人崩溃自白:全球爆火的 CSS 框架,竟养不起 8 个人

                            2026-1-10 18:18:41

                            0 条回复 A文章作者 M管理员
                              暂无讨论,说说你的看法吧
                            个人中心
                            购物车
                            优惠劵
                            今日签到
                            有新私信 私信列表
                            搜索