Rax跨端框架实(踩)践(坑)总(指)结(北)

1,979 阅读8分钟

[作者:芝兮]

背景

Rax是什么

Rax 是阿里巴巴应用最广泛的跨端解决方案,支持开发者通过类 React DSL 编写 Web、小程序、Flutter 等不同容器的跨端应用。

官方网站:rax.js.org/

Rax使用背景

实际业务中为了跨端,采用了Rax框架来进行开发,从而适配小程序和H5,过程中遇到了各种各样的业务场景和技术问题。以下内容是经验总结和趟坑指导指南,欢迎交流。

一、语法篇

1.如果使用x-if、x-elseif不生效

记得检查下,不要在x-elseif前面添加注释,不然指令会被忽略。

// 错误事例
<View x-if>
</View>
{/** 第二个 **/}
<View x-elseif>
</View>

//正确用法
<View x-if>
</View>
<View x-elseif>
</View>

2.文件中不能export多个对象

比如以下用法无法成功执行,如果使用了支付宝小程序IDE,则会直接白屏:

export { Component1, Component2, Component3 }

import { Component1 } from '../..'

3. jsx文件错误在小程序开发者工具中有些不会打印

经常有同学会反馈类似的问题:为啥我写完后页面老是白屏,是哪里不对?

举个栗子:组件忘记import,小程序开发者工具的控制台不会报错,只会直接白屏。

解决方案:H5调试的时候控制台有错误日志,但是小程序开发者工具中并没有。所以如果写的是跨端的代码,倾向使用H5来进行调试或者靠火眼金睛来找bug。

二、组件篇

1.Text组件不能包子组件

以下写法不支持,因为Text组件不能再包子组件。

<Text>aaaa<Text>bbbb</Text>cccc</Text>

2.Calendar组件不展示默认时间

目前的Calendar组件有value(设置的值)但是打开日历的时候,没有默认展示当前value日期。

解决方案:已经给Calendar组件那边提了MR,并且已经上线。

三、生命周期篇

1.usePageShow无法获取当前state中的最新数据

usePageShow 回调函数中无法获取当前state中的最新数据。根本原因是因为闭包。

具体可以查看以下文章来了解闭包的问题:《使用 JS 及 React Hook 时需要注意过时闭包的坑》

解决方案建议:usePageShow的回调函数中确实要获取最新的数据,就建议不要把数据存在state中,可以考虑存在全局或者有自己的store设计也可以。如果需要在usePageShow的回调函数中修改数据触发页面更新,则除了全局还需要维持一份数据到state来触发render。

2.原生中setData的回调函数如何处理(如何保证页面已经渲染完成)

原生支付宝小程序中有一些写法是在setData的回调函数中处理了一些逻辑,此时说明这些逻辑是需要在页面渲染更新完成之后执行的。

this.setData(
  {
    hasLoadPlugin: true,
  },
  () => {
    this.showSubscribe(subscribeMessage)
  },
)

支付宝小程序setData文档

在Rax中使用useState来更新数据,这样的话无法保证页面渲染完成,此时需要使用setDataFinished的事件来处理。举个例子:

import { createElement, useEffect } from 'rax';
import View from 'rax-view';
import { getBoundingClientRect } from '@uni/element';

function logBoundingClientRect() {
  console.log('=============')
  getBoundingClientRect('#container').then((ret) => {
    const { width, height, top, left, right, bottom } = ret[0];
    console.log(width, height, top, left, right, bottom);
  });
}

export default () => {
  useEffect(() => {
    window.addEventListener('setDataFinished', logBoundingClientRect);

    return () => {
      window.removeEventListener('setDataFinished', logBoundingClientRect);  
    }
  }, []);
  return (
    <View id="container">
    <View>1</View>
    <View>2</View>
    </View>
  )
}

使用这种方式要注意如果页面中触发渲染次数很多或者存在轮询/轮播的内容,则setDataFinished监听后的处理方法也会不断的被调用:

例如某个页面加了倒计时功能,加上上面的组件会发现,因为时间是轮询变化的,那也就意味着每一次变化都会触发setDataFinished事件,所以通过log发现一直会执行logBoundingClientRect方法。

建议:

  • 要进行removeEventListener
  • 方法内通过变量判断,如果已经执行过了,不要再次执行。

参考《在页面渲染后获取小程序原生节点信息或 context 失败,应该怎么处理?》

3.支付宝小程序声明周期和全局变量

支付宝小程序声明周期和全局变量,在不分包的情况下,可以直接写在根目录 src 下的 app.js 里,但是很多项目中是分包的,直接写在根目录的 app.js 里不生效,需要写在主包目录下的 app.js 里,全局变量声明要加一下环境判断,如下代码所示:

import { runApp } from 'rax-app'
import staticConfig from './app.json'
import { isMiniApp } from '@uni/env';

const appConfig = {
  app: {
    // 小程序应用生命周期
    onLaunch(options) {
      if (isMiniApp) {
        this.test = false
      }
    },
    onShow() {
      console.log('app onShow')
    },
    onHide() {
      console.log('app onHide')
    },
  },
}
runApp(appConfig, staticConfig)

四、框架API相关

1. getSearchParams获取路由参数(特殊字符)不对

根据官方文档说的,getSearchParams

仅支持 SPA 和小程序,不支持 MPA 应用

用于解析 url 参数。假设当前 URL 为 example.com?foo=bar 解析查询参数如下:

// src/components/Example
import { getSearchParams } from 'rax-app';

function Example() {
  const searchParams = getSearchParams()
  // console.log(searchParams); => { foo: 'bar' }
}

当参数foo的值包含特殊字符时(例如foo=bar&test),很容易想到要做encodeURIComponent处理,但是实践中发现,encodeURIComponent一次,getSearchParams得到的结果还是错误的(即foo=bar),只有encodeURIComponent2次getSearchParams得到的才是正确的结果(foo=bar&test)。

五、功能篇

1. 宽高获取

在原生支付宝小程序中经常可以看到通过createSelectorQuery查询DOM来获取宽高。

// 获取高度,回传自适应图区域
async getDemoHeight() {
  return new Promise((resolve) => {
    my.createSelectorQuery()
      .select('.component-demo')
      .boundingClientRect()
      .exec((res) => {
      resolve(res[0] && res[0].height)
    })
  })
},

在Rax中可以使用getBoundingClientRect API获取

import { createElement, useEffect } from 'rax';
import View from 'rax-view';
import { getBoundingClientRect } from '@uni/element';

function logBoundingClientRect() {
  getBoundingClientRect('#container').then((ret) => {
    const { width, height, top, left, right, bottom } = ret[0];
    console.log(width, height, top, left, right, bottom);
  });
}

export default () => {
  useEffect(() => {
    window.addEventListener('setDataFinished', logBoundingClientRect);

    return () => {
      window.removeEventListener('setDataFinished', logBoundingClientRect); 
    }
  }, []);
  return (
    <View id="container">
      <View>1</View>
      <View>2</View>
    </View>
  )
}

使用getBoundingClientRect时要注意,前面提到过,需要通过监听setDataFinished才能保证页面渲染完成后获取到DOM。

六、插件使用

1. 静态插件使用

根据文档可以调用静态插件

插件配置

src/app.json 配置 plugins 参数,和支付宝、微信原生的插件配置保持一致,例如

 "plugins": {
    "subscribeMsg": {
      "version": "*",
      "provider": "XXXXX"
    }
  },

插件组件使用

使用插件的组件时,直接使用 import 语法,例如

 import SubscribeMsgPlugin from 'plugin://subscribeMsg/subscribe-msg'

然后在 JSX 中正常使用 SubscribeMsgPlugin 标签即可

<SubscribeMsgPlugin />

插件 JS API 使用

使用插件 js 接口,无限制,使用 requirePlugin然后调用即可,和阿里、微信小程序官方使用方式保持一致

 if (requirePlugin) {
   const subscribeMsg = requirePlugin('subscribeMsg') || {}
   requestSubscribeMessage = subscribeMsg.requestSubscribeMessage
 }

多端开发注意

当开发多端时

import SubscribeMsgPlugin from 'plugin://subscribeMsg/subscribe-msg'

只能在支付宝小程序正常运行,而在微信端或者Web端都会出错,这个时候可以通过文件后缀来区分不同端的加载。参考: 《不同小程序端代码如何处理?》

2. Rax不支持动态加载插件

找了Rax相应的同学了解到目前Rax不支持动态加载插件,但是因为Rax可以支持使用原生,所有有了另外一种可能。在公共util中封装loadPlugin API,只有在支付宝小程序中才能使用。这种方案只能保证JS API loadPlugin是可以调用的,但是<component is=XXX />不支持,所以动态加载插件如果涉及到页面使用component渲染插件的话,是无法使用RAX的。

解决方案:如果动态加载的插件是一整个页面,不涉及<component is=XXX />可以直接用loadPlugin。如果涉及了,不能使用rax来实现(例如SubscribeMsg

《支付宝小程序插件说明》

七、功能无法跨端场景下,Rax中使用原生

类似于之前的动态加载插件功能,都只在原生支付宝小程序上生效,所以经常能够想到的解决方案就是在Rax中调用原生的组件。

使用原生方式

支付宝原生组件引入和使用方式:

直接将小程序原生组件代码拷贝至 src/miniapp-native 目录下,并按具体路径引用组件即可使用:

import SubscribeMsg from '../../miniapp-native/subscribe-msg/index'

(注:请勿使用 alias 路径)

使用的地方加上环境判断:

{env.isMiniApp && <SubscribeMsg entityIds={entityIds} />}

通过以上的方式,可以发现在支付宝小程序中,确实成功使用了原生小程序组件(动态引入了插件)

image.png

但是因为使用Rax是为了多端,此时会发现H5端编译报错(component is not defined),页面白屏,经过排查发现Rax编译H5时不是根据env.isMiniApp判断是否显示组件来动态加载的。所以在使用原生时要考虑解决多端差异化问题。

因为import 组件的操作必须在文件开头执行,无法加入逻辑判断动态引入,可按照后缀名加载对应端的文件来进行分端构建。在之前《多端开发注意》章节提到过。

八、样式篇

1. flex 布局

Rax View有自己的内置样式,默认flex布局,但是会发现这个默认flex-direction: column;这跟之前小程序中默认的row是不一样的,所以需要手动复写样式,需要的地方改成row。

尾言

以上是Rax开发的经验总结文档,希望对使用Rax框架开发的同学能带来一些收获,与君共勉之。