前言
使用 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 端運行時被覆蓋的問題。