需求背景
公司网页端地图实现使用的是天地图,且已经在其基础上开发了很多功能,现在要在 app
上也实现地图功能,但是 uni-app
官方的 Map
组件只支持 高德、谷歌、腾讯等。为了方便复用已有功能和避免重复购买,要想办法在 app
端也能使用 天地图
技术分析
已知 uni-app
打包出的 app
实际上是一个套壳的网页,在 webview
外层套了一层 app
的壳子,而在 webview
上肯定是支持使用 h5
的那一套东西的。那我们就得想办法操控 webview
,实际上 uni-app
官方提供了一个 webview 组件:
那么利用它我们肯定可以实现需求,但本文重点说的不是它,而是另一个有趣得东西——renderjs:
我们主要关注它的第二个主要作用:在视图层操作dom
,运行 for web
的 js
库,好,听起来不错,动手试试吧:
技术实现
页面结构和样式
这一块就比较简单,搞个容器用来展示地图
<template>
<view>
<view id="tmap-box"></view>
</view>
</template>
<style lang="scss" scoped>
#tmap-box {
width: 668rpx;
height: 900rpx;
}
</style>
页面逻辑
<script>
export default {
data() {
return {
}
}
}
</script>
<script module="TMap" lang="renderjs">
export default {
data() {
return {
tMapInstance: null, // 天地图
}
},
mounted() {
const tmapScript = document.createElement('script')
tmapScript.src = `https://api.tianditu.gov.***/api?v=4.0&tk=${'天地图密钥'}`
tmapScript.type = "text/javascript"
document.head.appendChild(tmapScript)
tmapScript.onLoad = this.initMap.bind(this)
},
methods: {
initMap() {
this.tMapInstance = new T.Map('tmap-box')
const lnglat = new T.LngLat(116.40969,39.89945)
this.tMapInstance.centerAndZoom(lnglat,12)
}
}
}
</script>
可以看到,我们搞了两个 script
标签,第一个就是我们平时页面的 script
,我们姑且称为 “原始模块”,第二个我们加了 module="TMap" lang="renderjs"
这两个属性,而这个 script
就是 renderjs
的模块,命名为 “TMap
模块”,而我们加载天地图的逻辑主要在 TMap
模块中。
效果
两个模块间通信
renderjs
模块向原始模块发送信息
在 renderjs
模块任意位置可以调用 this.$ownerInstance.callMethod('methodName', data)
来向原始模块发送信息。
而在原始模块中需要提前定义函数 methodName
:
methods: {
methodName(data) {
// 接收到 renderjs 模块传过来的信息 data
}
}
那我们可以在 renderjs
模块定义一个统一发送的函数,在原始模块中定义一个统一接收的函数:
// renderjs
methods: {
sendMsg(msg, data) {
this.$ownerInstance.callMethod('reciveMessage', {
msg,
data
})
}
}
// 原始模块
methods: {
reciveMessage(msgObj) {
console.log(msg.data)
switch (msgObj.msg) {
case 'a':
// ....
break
case 'b':
// ....
break
}
}
}
原始模块向renderjs
模块发送消息(renderjs
模块监听原始模块数据变化)
其实原始模块没法主动向 renderjs
模块发送消息,而是 renderjs
模块可以选择监听原始模块某些数据的变化
绑定监听
以我之前的例子来说,假如原始模块的 data
中有这些数据:
data() {
return {
flag: false,
str: '',
num: 0,
arr: [],
obj: {
a: false,
b: '111',
c: 0,
d: [],
e: {},
f: null
}
}
}
那么在原始模块需要这样绑定:
<template>
<view
:flag="flag"
:change:flag="TMap.handleFlagChange"
:str="str"
:change:str="TMap.handleStrChange"
:num="num"
:change:num="TMap.handleNumChange"
:arr="arr"
:change:arr="TMap.handleArrChange"
:obj="obj"
:change:obj="TMap.handleObjChange"
>
<view id="tmap-box"></view>
</view>
</template>
而在 TMap
模块中就需要定义对应的处理函数:
methods: {
handleFlagChange(nV, oV) {
// 页面刚加载的时候会触发一次,nV 打印 false,oV 打印 undefined
if(oV !== undefined) {
// 处理逻辑
}
},
handleStrChange(nV, oV) {
// 页面刚加载的时候会触发一次,nV 打印 '111',oV 打印 undefined
},
handleNumChange(nV, oV) {
// 页面刚加载的时候会触发一次,nV 打印 0,oV 打印 undefined
},
handleArrChange(nV, oV) {
// 页面刚加载的时候会触发一次,nV 打印 [],oV 打印 undefined
},
handleObjChange(nV, oV) {
// 页面刚加载的时候会触发一次,nV 打印 { a: false, b: '111', c: 0, d: [], e: {}, f: null },oV 打印 undefined
},
}
是不是有点向平时 vue
中使用的 watch
?,要注意的是,这些监听函数在页面刚加载的时候就会执行一次,打印的 nV
的值就是原始模块中绑定的初始值,oV
的值则都是 undefined
,所以要判断后再处理。
注意
renderjs
模块和原始模块不能互相访问对方 data
中绑定的属性的,即:
<template>
<view>
<view id="tmap-box"></view>
</view>
</template>
<script>
export default {
data() {
return {
flagA: false
}
},
mounted() {
console.log(this.flagB) // 报错
}
}
</script>
<script module="TMap" lang="renderjs">
export default {
data() {
return {
flagB: false
}
},
mounted() {
console.log(this.flagA) // 报错
}
}
</script>
而如果想在 renderjs
模块中直接访问原始模块中的数据,则需要像之前说的一样绑定属性和处理函数:
<template>
<!-- 不能只绑定属性,一定要同时绑定处理函数 -->
<view :flagA="flagA" :change:flagA="TMap.handleChangeFlagA">
<view id="tmap-box"></view>
</view>
</template>
<script>
export default {
data() {
return {
flagA: false
}
}
}
</script>
<script module="TMap" lang="renderjs">
export default {
mounted() {
console.log(this.flagA) // false
},
methods: {
handleChangeFlagA(nV, oV) {
// 不用做任何处理,但这个函数一定要有
}
}
}
</script>
至于想在原始模块中访问 renderjs
模块中的数据,目前好像没有办法,也可能是我不知道。
总结
renderjs
使用起来还是很麻烦的,但是它可以使你的 app
可以使用 for web
的库,这个作用还是很大的,而它的语法和使用方式,我也了解的不太全面,大家文章中也可能有错误的和不全面的地方,大家有懂得欢迎评论区指正,谢谢。