3

Taro3.2 适配 React Native 之运行时架构详解

 2 years ago
source link: https://my.oschina.net/u/5079097/blog/5056819
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Taro3.2 适配 React Native 之运行时架构详解

导读

由 58 前端团队主导的 Taro 3 适配 React Native 工作已完成有一段时间了。目前发布了多个体验版,也将在3月底迎来正式版。基于 Taro 的良好架构演变,适配 React Native 的方案的也做了较大调整,本文将主要介绍 Taro 3 适配 React Native 运行时相关的详细设计与实现。

背景

Taro 已经进入3.0 时代,相对于 Taro 1/2 来说,采用重运行时架构,可以让开发者能够获得完整的 React/Vue 等框架开发体验,因此,我们在设计 Taro3 React native 的方案时,也是基于运行时方案,增加 taro-runtime-rn 包来适配 React Native 端,使得 Taro 标准的 React 代码可运行在 React Native 端,让开发者可以低成本的扩展到 React Native 端。

方案设计

Taro 3.0 是近乎全运行时方案,在设计整个架构时,从浏览器的角度去思考,无论是开发框架是什么, React 也好, Vue 也罢,最终代码经过运行之后,都是调用浏览器的 BOM/DOM 的 API,因此,对于小程序端,Taro 团队增加 taro-runtime 包,在这个包中实现一套高效、精简版的 DOM/BOM API, 当运行在小程序端时,也有一套高效的 DOM/BOM API,从而实现了跨框架开发方案。
详细内容参考( https://mp.weixin.qq.com/s/5pdUD9YNojgvZBSve5-2EA

在设计 Taro3 React Native 方案之初,我们希望可以与小程序端标准较为一致的方案,对比了两种方案:

  • 支持 React, Vue 开发,与小程序的设计思路一致,让 React Native 去模拟浏览器的 BOM/DOM API ,实现 React Native 的 render

  • 支持 React 开发,通过编译和运行时去适配 Taro 的写法

以上两种方案,如果采用基于小程序的方案,会存在以下问题:

  1. 基于小程序的方案,运行在 React Native 端,性能会有所下降,且方案更加复杂化,维护成本太高

  2. 脱离 React Native 生态,比如一些原本可直接使用的组件,需要做一层适配才可使用

因此,我们采用第二种方案,更好的贴近 React Native 生态,通过编译和运行时适配,让 Taro3 的 React 代码可以方便的扩展到 React Native 端。

详细设计

Taro3 React Native 整体方案的设计思路:基于 Taro 源码,利用 Metro 打包直接生成 jsBundle,通过编译和运行时适配到 Taro 的写法。

21A99688C4EDA97BB094D3BCF32CA1E5.jpg

https://mp.weixin.qq.com/s/-7G7NMHX8ol99QxkswFOxg

直接基于源码去打包运行时适配,如何做适配,需要适配哪些内容?

  1. 入口文件及配置,Taro 入口写法是基于小程序的方案,需将其转换为 React Native 的入口及路由导航系统

  2. 页面的配置,对下拉属性,滚动,页面 Title 等相关设置

  3. 生命周期, componentDidShow, componentDidHide 的支持

  4. 页面函数,onPullDownRefresh, onPageScroll 等

  5. 事件、Taro 自定义 Hooks、 Current 对象

  6. 样式的支持,这里不过多的赘述,后续会有详细的文章说明

Taro3 React Native 是整体方案是利用 Metro 基于 Taro 源码打包。

Metro 是针对 React Native 的 JavaScript 模块打包工具,接收一个入口文件和打包配置,将项目中所有依赖打包在一个或多个js文件。

打包过程会分为三个阶段:

  • Resolution:构建模块的依赖图,处理模块的依赖关系

  • Transformation:负责将模块转换成目标平台可以理解的格式

  • Serialization:模块转换序列化,组合这些模块来生成一个或多个包

对于 Taro 写法的支持,我们在 Transformation 转化阶段,通过自定义的 taro-rn-transformer 与 taro-rn-style-transformer 对 Taro 的代码进行转换。

1B5B1F5AC69ED7D12159964CDD76876C.jpg

  • 在编译阶段,页面源文件都会进入到自定义的 taro-rn-transformer ,在 rn-transformer 中,会根据编译配置,一是,入口及页面会
    注入运行时处理函数, 二是,在 React Native 中,样式并没有全局概念,对于 Taro 中定义的全局样式,比如 app.scss 等,在进入到 rn- transformer,会全局样式引入到页面中,支持到全局样式。

  • 在代码运行阶段,运行时处理函数会适配到Taro的相关内容,包含动态构建导航,页面配置,生命周期函数等相关内容,完成对入口,导航与页面的支持。

Taro 3 React Native中,运行时方案主要包含三个模块 ,各个模块之间的关系:

4126BC2C0B346C4032E00C618F1D2402.jpg

  • taro-router-rn,基 React Navigation 对路由进行封装,提供动态创建导航的方法给运行时,并且封装导航相应的API内容。

  • taro-runtime-rn,运行时的核心内容,主要提供两个包装方法,一是 createReactNativeApp,用来对页面入口的相关处理,二是 createPageConfig,页面的包装方法,完成对页面的适配,包含生命周期,页面的配置等。

  •  taro-rn-transformer, 编译时注入页面的包装方法和入口方法,并将入口的全局样式注入到页面中。

对于 Taro 运行时的适配的内容,如图所示:

BC22A99E1F2C91ECBF8C3B93DC6871DA.jpg

入口文件支持

在 React Native 中,AppRegistry 是所有 React Native 应用的 JS 入口,通过 AppRegistry.registerComponent 方法注册根组件,若有多个页面,在根组件中建立对应导航系统。

在 Taro 中,入口是按照小程序方案来定义,要运行在 React Native 端,需将这些配置转换为 React Native 相关的配置,生成可运行在 React Native 的入口文件。

Taro 中入口文件:

//app.config.tsexport default {  pages:[    'pages/index/index',    'pages/index/about'  ],  window:{      backgroundTextStyle: 'light',      ....  },  tabBar:[...]}
//app.tsxexport defalut class App extends Component{    ...    render(){        return this.props.children    }}

我们实现方案的基本思路是:
读取 app.config ,获取到对应的页面信息,将页面在入口文件引入,建立起引用关系,根据页面路径转换为驼峰的形式来作为页面名称,生成构建导航系统的路由配置。

运行时模块会提供一个入口包装的函数,将全局配置,转换后的路由配置,动态的构建入口根组件。

转换后的入口文件代码:

import { AppRegistry } from 'react-native';import { createReactNativeApp } from '@tarojs/runtime-rn'
import App from './src/app'import pagesIndexIndex from './src/pages/index/index';import pagesIndexAbout from './src/pages/index/about'
var config = {"appConfig":{"pages":["pages/index/index","pages/index/about"],"window":{"backgroundTextStyle":"light","navigationBarBackgroundColor":"#fff"}}}const routers = [    {      name:'pagesIndexIndex',      component: pagesIndexIndex  },{    name:'pagesTabbarHome',    component: pagesIndexAbout  }]AppRegistry.registerComponent('app',createReactNativeApp(App,{config,routers}) 

运行时调用 createReactNativeApp 函数,在这个函数完成初始化,这个函数里主要做了些什么?

  1. 根节点Provider的注入

  2. 导航初始化

function createReactNativeApp (App,config){    return class Entry extends React.Component{        ...        render(){            return React.createElement(TCNProvider, { ...this.props },                     React.createElement(App, { ...props },                createRouter(config.routerConfig)          ))        }    }}
  • 对于根节点Provider注入,由于在 taro-component-rn 的 Picker 组件是封装的 Ant-Design 组件,需要注入 Ant-Design 的 Provider

  • 对于导航系统初始化,Taro 3 仍然是采用 React Navigation,和 Taro 1/2 的差别是,升级到了 5.x 的版本。封装导航模块,根据转换生成的路由配置,提供 createRouter 的方法,动态去创建路由节点,构建出导航系统

实现对页面支持,其基本思路和入口一致的,在编译阶段,注入页面包装的函数,在运行时阶段,完成页面配置,页面函数等相关支持。

import { createPageConfig } from '@tarojs/runtime-rn'
//页面文件import pagesIndexIndex from './src/pages/index/index'//页面configimport pageConfig from './src/pages/index/index.config'
export default createPageConfig(pagesIndexIndex,pageConfig)

在 Taro 页面组件中,根据页面适配,需实现对以下两个内容的支持。

  • 页面函数,包括了 onReactBottom, onPullDownRresh 等

  • 生命周期函数,包含了 componentDidShow, componentDidHide,这两个函数对应小程序的 onHide, onShow

在 React Native 端,也保持和 Taro 的 React 组件写法是完全一致, 通过运行时函数 createPageConfig,实现对于面函数与生命周期函数的支持。

页面函数支持

对于微信的页面函数,根据页面config配置文件来控制是否触发,
disableScroll 是否可滚动, enablePullDownRresh 是否开启下拉刷新。

  1. 对于onPageScroll,onReachBottom , onPullDownRresh 都与页面滚动相关联, 当 config 配置 disablecroll 不为true时,对应的页面最外层会用 ScrollView 包含对应的页面组件,实现对页面函数的支持

  • onPageScroll, 通过监听 ScrollView 的 onScroll 方法实现

  • onReachBottom, 监听页面滚动动画结束函数 onMomentumScrollEnd ,来判断当前的离底部高度,最终来触发该函数

  • onPullDownRresh, 当enablePullDownRresh为 true 时,开启下拉刷新,通过封装 refreshControl 来实现

    function createPageConfig(Component,config){    const WrapScreen = (PageComponent) => {       return class PageScreen extends React.Component{            ...            render(){            return <ScrollView                    ...                    onScroll={()=>{}}                    refreshControl={<RreshControll />}                   >                    <PageComponent />                </ScrollView>            }        }     }    return WrapScreen(Component)}
  1. onResize , onTabItemTap ,是基于 React Native 现有方案的实现

  • onResize, 在 React Native中,可监听屏幕高度变化,在 Taro 中,是通过监听屏幕的宽高变化来触发该方法

  • onTabItemTap , TabBar 是和导航相关联,我们导航是基于 React Navigation 的封装,监听导航的 tabPress 方法来触发 onTabItemTap

生命周期支持

对于生命周期函数 componentDidShow, componentDidHide,这两个函数的触发条件:

  1. 当页面发生跳转时

  2. 当App进行前后台切换的时

实现上述函数,基本思路:

  • App前后台切换时,通过监听 AppState 的状态变化,状态切换的变化,可判断是从前台到后台,从而来触发对应的函数

  • 我们的路由导航系统是基于 React Navigation, 页面切换时,导航提供了页面聚焦和是失去焦点时触发 focus 与 blur 事件,通过监听这两个事件,判断当前页面是否可见来触发对应函数

Current对象

在 Taro 3.0 之后,小程序端没有自定义组件,也不再有 this.scope 和 this.componentType,this.$router 的概念,对于需要获取页面切换的参数,当前页面的实例对象,通过提供了 getCurrentInstance 方法,返回 Taro 全局变量 Current ,包含路由,应用与页面实例,包含三个属性:

  • Current.app,返回当前小程序应用实例

  • Current.page,返回当前小程序页面实例,

  • Current.router,返回当前小程序路由信息

在 React Native 端,也是调用 getCurrentInstace 方法来返回 Current 对象

  • 对于 app 和 page ,返回小程序规范实例,可通过此实例调用小程序规范生命周期。其实现思路是,当页面切换时创建一个对象,对象包含小程序的生命周期方法,当调用该方法时,通过 ref 关联到的当前页面,来 call 当前页面的方法。

 const pageRef = this.screenRef const inst: PageInstance = {    config: pageConfig,    route: pagePath,    onShow () {        const page = pageRef.current        if (page != null && isFunction(page.componentDidShow)) {            page.componentDidShow && page.componentDidShow()        }     },    ...   }
  • 对于 router,基于 React Navigation 导航 获取到路由参数,返回到 router 对象中,目前暂不支持 onReady 等生命周期方法

原生 React Native 应用支持

有开发者提到,对于目前已经存在的 React Native 项目,在不修改原来的页面和导航的前提下,是否可以接入Taro?

答案是肯定的,基于 Taro 3 整体的设计方案,与现有业务的结合接入,我们也给出了对应的方案。

对于已有 React Native 项目接入 Taro,需要支持以下几点:

  1. 与原有页面一起打包方案结合

  2. 路由需要统一处理

  3. 支持 Taro 的编译配置,页面函数等

关于打包方案,Taro3 React Native 的打包方案是基于 Metro , 编译打包会生成支持Taro的 Metro 配置,并与业务配置合并得到最终的配置进行打包,能够很好的与现有业务进行融合。

关于路由统一处理,Taro React Native 的路由是基于页面的配置,封装的React Navigation的方案,与现有业务的路由结合,入口仍然按照原来的方式,Taro 页面路由可自行加入,完成路由的处理。

因此,我们提供了一种比较灵活的接入方案,其基本思路:支持导出 Taro 默认的 Metro 配置,与业务配置合并得到最终打包配置,提供支持Taro写法的运行时方法,处理页面编译配置,页面函数等相关内容

  • 提供 tarojs/rn-supporter 的包, 导出Taro3 的 Metro 配置,可支持以下内容

    • 支持Taro样式写法

    • 支持Taro编译配置

    • 支持Taro运行时配置

    • 支持Taro跨平台开发方案

758921696591E68000F7F549AF41B3D0.jpg

  • 提供运行时的函数,通过直接的调用运行时的包装函数,完成对页面内容的支持

    • 支持页面函数,生命周期函数等

    • 支持页面配置

import { createPageConfig } from '@tarojs/runtime-rn'import PagesTaroPageApi from './src/pages/taroPage/api'import PagesTaroPageApiConfig from './src/pages/taroPage/api.config'
<Stack.Screen    name="PagesTaroPageApi"    component={createPageConfig(PagesTaroPageApi, { pagePath: '/pages/taroPage/api',...PagesTaroPageApiConfig})} />

这种方案可以较为方便的和现有项目结合,但需注意两点:

  1. 业务需自行处理导航

  2. 页面组件需要运行时函数包装

总结

Taro3 React Native 是基于 Metro 打包,通过自定义 transformer 来适配 Taro 的样式和页面支持,提供运行时函数,可以方便的支持到 Taro 页面配置与相关函数 ,更加的方便灵活,也更加贴近React Native生态,也可更方便的与现有业务融合,在不跨端的项目中也可以使用,能够大大提升我们的开发效率。

当然,我们的方案也还还存在进一步优化的空间,比如支持组件与API运行时自定义扩展,在不同的业务中,有些组件和API存在差异性,如地图,跟业务有一定的关联性,可按需要接入百度或高德地图等。

完整实例:http://github.crmeb.net/u/defu

来自 “开源世界 ” ,链接:http://ym.baisou.ltd/post/599.html,如需转载,请注明出处,否则将追究法律责任。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK