需求背景
公司网页端地图实现使用的是天地图,且已经在其基础上开发了很多功能,现在要在 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.cn/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
模块定义一个统一发送的函数,在原始模块中定义一个统一接收的函数:
// renderjsmethods: { 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
的库,这个作用还是很大的,而它的语法和使用方式,我也了解的不太全面,大家文章中也可能有错误的和不全面的地方,大家有懂得欢迎评论区指正,谢谢。