前言
使用 UniApp 开发 Android / iOS App 时,经常会有地图、可视化图表、摄像头、视频播放、人脸采集、富文本编辑器等需求。而 UniApp 提供的相关组件或多或少存在各种影响使用的问题:如地图层级过高难以覆盖,UniApp 插件市场中的可视化图表功能缺失、摄像头相关 API 接口过于简陋等,使得开发工作难以进行。
RenderJS
在这种情况下,RenderJS 的出现极大程度上缓解了这些难题。 RenderJS 是内置在 UniApp 框架中的一个组件,它运行在视图层,可以在 App 端中对 DOM 直接进行操作,并运行 for web 的 JavaScript 库,这意味着在 Web 支持的一切功能都可以在 App 端实现。
与此同时, RenderJS 也存在着一定的限制:如不支持 Vue3 的 Setup 写法,不能直接和逻辑层进行通信,需要借助一定的技巧才能实现双向通信等。
示例代码
下面是 RenderJS 实际使用的一点例子,这些代码想说明的问题只有一个:视图层和逻辑层如何互相调用方法和传递数据。示例比较长,可以翻到下面跟着说明来阅读代码。
<template>
<view id="container" style="padding-top: 20vh;" :viewLayerCallJson="viewLayerCallJson"
:change:viewLayerCallJson="viewLayer.handleLogicLayerCall" :viewLayerData="viewLayerData" :change:viewLayerData="viewLayer.handleLogicLayerData">
<button @click="handleTest">调用逻辑层方法</button>
<button @click="viewLayer.handleViewLayerTest">调用视图层方法</button>
<button @click="handleTest2">从逻辑层调用视图层方法</button>
<button @click="viewLayer.handleViewLayerTest2">从视图层调用逻辑层方法</button>
<button @click="handleSendDataToViewLayer">视图层传递数据给视图层</button>
<button @click="handleTest4">动态创建 VIDEO 元素</button>
</view>
</template>
<script>
export default {
data() {
return {
viewLayerCallJson: null,
viewLayerData: null
};
},
methods: {
handleViewLayerCall({ method, params }) {
this[method]?.(params);
},
handleCallViewLayerFunc(method, params) {
const randomNethodPrefix = Math.random().toString(36).substring(2, 15);
this.viewLayerCallJson = JSON.stringify({
method: `${randomNethodPrefix}-${method}`,
params
});
},
handleTest2() {
this.handleCallViewLayerFunc("createEl", {
tag: "input",
value: "hahahaha"
});
},
handleTest3({ content }) {
uni.showModal({
title: "提示",
content,
success: (res) => {
console.log(res);
}
});
},
handleTest4() {
this.handleCallViewLayerFunc("createEl", {
tag: "video"
});
},
handleTest() {
uni.showModal({
title: "提示",
content: "逻辑层方法被调用啦",
success: (res) => {
console.log(res);
}
});
},
handleSendDataToViewLayer() {
this.viewLayerData = Date.now();
}
}
}
</script>
<script lang="renderjs" module="viewLayer">
export default {
data() {
return {
_data: {}
};
},
methods: {
handleViewLayerTest() {
alert("视图层方法被调用啦")
},
handleViewLayerTest2() {
this.callLogicLayerFunc("handleTest3", {
content: "从视图层调用逻辑层方法"
});
},
handleLogicLayerCall(json) {
if (!json) return;
const { method: randomMethod, params } = JSON.parse(json);
const method = randomMethod.split("-")[1];
this[method]?.(params);
},
createEl({ value, tag }) {
const el = document.createElement(tag);
if (value) {
el.value = value;
}
document.querySelector("#container").append(el);
},
callLogicLayerFunc(method, params) {
this.$ownerInstance.callMethod("handleViewLayerCall", {
method,
params
});
},
handleLogicLayerData(newval, oldval, owner, instance) {
console.log(newval, oldval, owner, instance);
}
}
}
</script>
<style>
button + button {
margin-top: 20px;
}
</style>
编写 RenderJS 代码
写在正常 script 标签中的代码运行在逻辑层,而 RenderJS 运行在视图层。 RenderJS 的代码在编写时需要单独写在一个 script 标签中,同时给 script 标签添加 lang="renderjs" 和 module="xxx" 的属性,前者是固定值不能修改,后者可以自行定义,用来在模板中引用 RenderJS 中定义的属性和方法。
逻辑层向视图层传递数据
传递数据需要关注以下几点:
- 在逻辑层中定义数据,这个无需多言,正常定义要传递的数据即可。
2. 在模板内任意标签中绑定要传递的数据,通过 :change:[属性名] 来监听绑定数据的变化,并绑定在视图层中定义的回调函数,绑定视图层中定义的回调函数时需要以视图层 module 属性为前缀来调用。
3. 在视图层内定义属性变化的回调函数
回调函数会传递四个参数,分别是绑定属性新值,上一次的值,视图层实例以及逻辑层实例,需要注意回调函数在组件创建之后无论数据是否变化都会被调用一次。
视图层向逻辑层传递数据
视图层并不能直接向逻辑层传递数据,只能借助调用逻辑层方法的形式来传递数据,具体可以参见下一小节。
视图层调用逻辑层方法
视图层可以通过 $ownerInstance.callMethod 方法来调用逻辑层内定义的方法,该方法接受两个参数,逻辑层方法名和逻辑层方法接受的参数。
逻辑层调用视图层的方法
逻辑层并不能直接调用视图层的方法,但通过一些技巧可以巧妙的实现调用。通过将要调用的方法和参数名序列化为 JSON 字符串,并作为数据传递给视图层,视图层在回调方法内反序列化 JSON 数据,然后执行相关方法。
如果连续调用同一个方法并传递相同的参数,由于 JSON 字符串没有发生变化,因为视图层的回调函数并不会被执行,因此需要在 JSON 字符串内加入一些随机因子,使得每次调用方法生成的 JSON 字符串总是不相同的。
需要注意传递的数据内容必须是兼容 JSON 的基本数据类型,如果传递 Set、Map、ArrayBuffer 等类型将会出现不可预知的错误,同时传递的数据也不宜过大过于频繁,否则可能会造成性能问题。
使用场景
基于以上的例子,以往很多不能实现的操作都可以实现。如引入 ECharts 使用全功能图表,通过动态创建 Video 元素来避免使用 App 内置的 Video 元素,使用 Web 端的各种地图组件,不再为 App 端 Map 组件层级过高无法覆盖而头痛,使用 navigator.getUserMedia 操作摄像头来实现自定义视频录制界面等等,一切在 Web 端可以实现的功能都可以在 App 中实现,不再受限于 UniApp App 的 API 限制。
注意事项
RenderJS 仅兼容 App 和 Web 两端,虽然在微信小程序中存在着类 RenderJS 的组件 WXS,但其只是 RenderJS 的剪裁版,因此并不兼容。
RenderJS 是一个用来在 App 中实现 Web 相关能力的产物,因此在 Web 中并不需要 RenderJS。 RenderJS 在 Web 端运行时,其会以 mixin 形式混入逻辑层,因此在编写视图层代码时,应注意在属性名和方法名上和逻辑层加以区分,以免出现在 Web 端运行时被覆盖的问题。