uniapp当前痛点
采用uniapp框架开发时,很多情况下会收到第三方原生生态的制约,一些第三方的服务可能只有安卓、ios、web的sdk,并不会单独对uniapp开发适配的sdk。如果想要用uts的方式进行接入,需要每个端都写一套适配的代码(安卓、ios或鸿蒙),这样带来的开发成本会很高。而且引入过多的sdk会提高app的内存大小,在云打包时安卓如果超过150MB,每打包一次都要收费。
我们的项目就遇到了这样的问题,使用到的第三方服务没有提供uniapp版本的插件,需要手动的接入原生的sdk,非常复杂,而且sdk还有很多类型声明和官方文档上的不一致,成了开发的卡点。
通过翻阅uniapp文档,我们发现了一种叫做renderjs的方式,可以在视图层运行的JS。
renderjs介绍与使用
官方文档:https://uniapp.dcloud.net.cn/tutorial/renderjs.html#renderjs
uniapp打包的app,其实是一种桥接技术,将原生能力和webview桥接起来,所以在官方文档的划分中,有逻辑层和视图层两个概念。我们编写的代码都会运行在逻辑层中,通过一种特定的通信来控制视图层,所以这样会有不少的性能损耗,也是代码里不能频繁的更新视图的原因。在逻辑层中,也没有dom相关的元素给我们使用,而视图层不一样。
视图层由webview渲染,拥有dom和window,renderjs天然的处于webview环境中,可以直接操作视图dom,毫无通讯损耗。最关键的是它相当于在浏览器环境运行的代码,可以接入第三方的web端sdk,正常的获取dom和window元素,不会像逻辑层的代码一样,抛出各种不兼容。
使用renderjs,需要先在template中挂载view标签,并单独写一个script。代码如下:
<template>
<!-- renderjs数据绑定 -->
<view :prop="rtmConfig" :change:prop="agora.handleConfig" style="display: none"></view>
</template>
<script lang="ts" setup>
// 通知renderjs使用RTM加入频道
rtmConfig.value = {
userId: 'xxxxxxxx'
};
/**
* 处理renderjs消息(通过事件总线)
*/
const handleRenderjsMessage = (message: any) => {
console.log('收到renderjs消息:', message);
};
onBeforeMount(async () => {
// 监听renderjs消息事件(使用事件总线)
uni.$on('renderjs-message', handleRenderjsMessage);
});
</script>
<!-- Vue 2 桥梁:用于 renderjs 调用,可以使用 uni.$emit -->
<script lang="ts">
export default {
methods: {
// renderjs 调用此方法发送消息给 Vue 3
handleRenderjsBridge(data: any) {
try {
console.log('Vue2桥梁收到消息:', JSON.stringify(data));
// 使用 uni.$emit 发送事件给 Vue 3
uni.$emit('renderjs-message', data);
} catch (error) {
console.error('Vue2桥梁发送消息失败:', error);
}
}
}
};
</script>
<script module="agora" lang="renderjs">
export default {
data() {
return {
// #ifdef APP
sdkUrl: 'static/js/xxx.js',
// #endif
// #ifdef H5
sdkUrl: '/static/js/xxx.js',
// #endif
ownerInstance: null
}
},
mounted() {
console.log('Agora RenderJS mounted - 等待配置');
},
methods: {
// 初始化Agora
async initAgora() {
try {
console.log('正在初始化...');
// 动态加载Agora RTM SDK
await this.loadAgoraSDK();
console.log('初始化完成');
} catch (error) {
console.error('初始化失败:', error);
}
},
// 动态加载Agora SDK
loadAgoraSDK() {
return new Promise((resolve, reject) => {
console.log('准备加载SDK,路径:', this.sdkUrl);
const script = document.createElement('script');
script.src = this.sdkUrl;
script.onload = () => {
console.log('ASDK加载成功');
resolve();
};
script.onerror = (error) => {
console.error('SDK加载失败,错误:', error);
console.error('尝试加载的URL:', this.sdkUrl);
};
document.head.appendChild(script);
});
},
// 处理配置 - 使用uni-app renderjs规范
handleConfig(newValue, oldValue, ownerInstance, instance) {
console.log('收到配置信息:', JSON.stringify(newValue));
// 保存ownerInstance供后续使用
this.ownerInstance = ownerInstance;
// 配置接收后初始化sdk
this.initAgora();
},
// 发送消息给逻辑层
sendMessage(data) {
try {
console.log('正在发送消息给逻辑层:', JSON.stringify(data));
// 通过 ownerInstance.callMethod 调用 Vue 2 的方法
// Vue 2 方法中可以使用 uni.$emit
if (this.ownerInstance && this.ownerInstance.callMethod) { this.ownerInstance.callMethod('handleRenderjsBridge', data);
} else {
console.warn('ownerInstance.callMethod 不可用');
}
} catch (error) {
console.error('发送消息失败:', error);
}
}
}
}
</script>agora可以自定义命名,rtmConfig是正常的逻辑层代码的变量,当rtmConfig绑定的数据发生改变后,会立即通知到agora的handleConfig中,这样我们就实现了逻辑层传递内容到renderjs中,函数有四个参数:
newValue: 变化后的新值
oldValue: 变化前的旧值
ownerInstance: 组件实例对象,可以获取逻辑层的函数,ownerInstance.callMethod('handleRenderjsBridge', data);
instance: 当前 renderjs 模块所在视图层的实例对象,通过 `instance.$el` 可以直接获取当前绑定的 DOM 元素通过document.createElement('script')挂载js,即可实现加载第三方sdk,当sdk处于本地时,h5与app上路径有些区别,app中路径为'static/js/xxx.js',h5中使用'/static/js/xxx.js'。
逻辑层通知renderjs,修改绑定的变量即可,但是renderjs通知给逻辑层就有一些曲折。
因为renderjs当前只能支持vue2的代码,不支持调用setup形式中的函数,我们的处理方案是写一层vue2的script作为桥接,vue2与setup的业务逻辑中通过uni.$emit全局通讯的方式传递。
这里有一个踩坑点需要注意:renderJs需要写在页面级别文件中,不能放在组件里嵌套,绑定的dom元素也需要在页面级别,不能嵌套,不然会报错Mt.setAttribute is not a function。当时排查了很久,发现从组件改到页面级文件就可以了。
使用效果
我们使用这套方案接入了声网RTM的sdk和国外的Stripe支付sdk,减少了很多的开发工作。在后续处理一个视频需求时,发现插件市场也有同样思路处理的视频组件,使用体验非常好,避免了很多原生video的问题:https://ext.dcloud.net.cn/plugin?id=19654
以上。
评论