Taro跨三端平台的应用与实践

背景

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 的代码,其核心利用的就是计算中非常基础的编译原理知识,将输入语言代码,通过编译器执行,输出目标语言的代码。编译原理的一般过程就是,输入源程序,经过词法分析、语法分析,构造出语法树,再经过语义分析,理解程序正确与否,再对语法树做出需要的操作与优化,最终生成目标代码。

image.png

将 JSX 编译成小程序模板,非常幸运的是 babel 的核心编译器 babylon 是支持对 JSX 语法的解析的,我们可以直接利用它来帮我们构造 AST,而我们需要专注的核心就是如何对 AST 进行转换操作,得出我们需要的新 AST,再将新 AST 进行递归遍历,生成小程序的模板。

源码改造

我们是基于Taro1.3.10版本开发的,为了使Taro兼容58RN工程我们对源码做了以下改造:
taro脚手架taro-cli

  1. 升级内部react-native版本"react-native": "0.57.8"
  2. 固定注册包名AppRegistry.registerComponentwuba

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

image.png

图上是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。

  1. 在开发中必须声明flex布局和排版,因为RN和小程序H5默认横纵不一样。
  2. position: 'absolute'需要在外层父view设置position: 'relative'。
  3. 覆盖组件样式可以通过style传递,但style不支持数组。

最终看一下实现的效果还是能保证高度统一的。


image.png

开发中遇到的问题

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同学共同促进通用层优化,使业务开发者可以专注于业务本身。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,230评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,261评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,089评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,542评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,542评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,544评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,922评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,578评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,816评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,576评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,658评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,359评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,920评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,859评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,381评论 2 342