Uncaught TypeError: this._map is null (Vue.js 3, Leaflet)
Uncaught TypeError: this._map is null (Vue.js 3, Leaflet)
我在 Vue.js 项目(版本 3)的 Leaflet 中收到一个奇怪的错误。
如果我关闭弹出窗口并缩放 in/out,Firefox 上会出现此错误:
Uncaught TypeError: this._map is null
在 Chrome 上:
Cannot read property '_latLngToNewLayerPoint' of null
地图组件如下:
<template>
<div id="map"></div>
</template>
<script>
import "leaflet/dist/leaflet.css";
import L from 'leaflet';
export default {
name: 'Map',
data() {
return {
map: null
}
},
mounted() {
this.map = L.map("map").setView([51.959, -8.623], 12);
L.tileLayer("https://{s}.tile.osm.org/{z}/{x}/{y}.png", {
attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(this.map);
L.circleMarker([51.959, -8.623]).addTo(this.map)
.bindPopup('I am a marker')
.openPopup();
}
}
</script>
<style scoped>
#map {
height: 300px;
width: 100%;
}
</style>
如何重现错误:
- 打开 stackblitz:https://stackblitz.com/edit/vue-gjeznj
- 关闭弹出窗口
- 缩放in/out
这只是一个错误吗?还是我遗漏了任何代码错误?
阅读了arieljuod的link,这似乎是唯一的选择,不调整Leaflet的js。文件,是为了禁用缩放动画。
this.map = L.map("map", {zoomAnimation: false})
如果需要动画,建议在此处对 Leaflet 的 js 文件进行小的调整:https://salesforce.stackexchange.com/a/181000
FWIW,这似乎是自 Vue 3 以来的新问题。
带有 Leaflet 的 Vue 版本 2 中不存在该问题:https://codesandbox.io/s/fast-firefly-lqmwm?file=/src/components/HelloWorld.vue
为了确保,这里是在 CodeSandbox 上用相同代码但 Vue 版本 3 重现的问题:https://codesandbox.io/s/laughing-mirzakhani-sgeoq?file=/src/components/HelloWorld.vue
罪魁祸首似乎是 Vue 对 this.map
的代理,这似乎干扰了 Leaflet 事件(取消)绑定。看起来 Vue 3 现在自动执行深度代理,而 Vue 2 是浅层的。
如https://v3.vuejs.org/api/basic-reactivity.html#markraw所述:
[...] the shallowXXX APIs below allow you to selectively opt-out of the default deep reactive/readonly conversion and embed raw, non-proxied objects in your state graph. They can be used for various reasons:
- Some values simply should not be made reactive, for example a complex 3rd party class instance, or a Vue component object.
...这是 Leaflet 构建 map
对象的情况。
一个非常简单的解决方法是不使用 this.map
(即不在组件状态中存储 Leaflet 构建的 map
对象,以防止 Vue 代理它),而只是存储它在本地(例如 const map = L.map()
然后是 myLayer.addTo(map)
)。
但是如果我们确实需要存储地图对象怎么办,通常是为了以后可以重新使用它,例如如果我们想在用户操作上添加一些层?
然后确保正确 unwrap / unproxy this.map
在与 Leaflet 一起使用之前,例如使用 Vue 3 toRaw
utility function:
Returns the raw, original object of a reactive
or readonly
proxy. This is an escape hatch that can be used to temporarily read without incurring proxy access/tracking overhead or write without triggering changes.
import { toRaw } from "vue";
export default {
name: "Map",
data() {
return {
map: null,
};
},
mounted() {
const map = L.map("map").setView([51.959, -8.623], 12);
L.tileLayer("https://{s}.tile.osm.org/{z}/{x}/{y}.png", {
attribution:
'© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);
L.circleMarker([51.959, -8.623])
.addTo(map)
.bindPopup("I am a marker")
.openPopup();
this.map = map;
},
methods: {
addCircleMarker() {
L.circleMarker([
51.959 + Math.random() * 0.05,
-8.623 + Math.random() * 0.1,
])
.addTo(toRaw(this.map)) // Make sure to "unproxy" the map before using it with Leaflet
.bindPopup("I am a marker")
.openPopup();
},
},
}
演示:https://codesandbox.io/s/priceless-colden-g7ju9?file=/src/components/HelloWorld.vue
我在 Vue.js 项目(版本 3)的 Leaflet 中收到一个奇怪的错误。
如果我关闭弹出窗口并缩放 in/out,Firefox 上会出现此错误:
Uncaught TypeError: this._map is null
在 Chrome 上:
Cannot read property '_latLngToNewLayerPoint' of null
地图组件如下:
<template>
<div id="map"></div>
</template>
<script>
import "leaflet/dist/leaflet.css";
import L from 'leaflet';
export default {
name: 'Map',
data() {
return {
map: null
}
},
mounted() {
this.map = L.map("map").setView([51.959, -8.623], 12);
L.tileLayer("https://{s}.tile.osm.org/{z}/{x}/{y}.png", {
attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(this.map);
L.circleMarker([51.959, -8.623]).addTo(this.map)
.bindPopup('I am a marker')
.openPopup();
}
}
</script>
<style scoped>
#map {
height: 300px;
width: 100%;
}
</style>
如何重现错误:
- 打开 stackblitz:https://stackblitz.com/edit/vue-gjeznj
- 关闭弹出窗口
- 缩放in/out
这只是一个错误吗?还是我遗漏了任何代码错误?
阅读了arieljuod的link,这似乎是唯一的选择,不调整Leaflet的js。文件,是为了禁用缩放动画。
this.map = L.map("map", {zoomAnimation: false})
如果需要动画,建议在此处对 Leaflet 的 js 文件进行小的调整:https://salesforce.stackexchange.com/a/181000
FWIW,这似乎是自 Vue 3 以来的新问题。
带有 Leaflet 的 Vue 版本 2 中不存在该问题:https://codesandbox.io/s/fast-firefly-lqmwm?file=/src/components/HelloWorld.vue
为了确保,这里是在 CodeSandbox 上用相同代码但 Vue 版本 3 重现的问题:https://codesandbox.io/s/laughing-mirzakhani-sgeoq?file=/src/components/HelloWorld.vue
罪魁祸首似乎是 Vue 对 this.map
的代理,这似乎干扰了 Leaflet 事件(取消)绑定。看起来 Vue 3 现在自动执行深度代理,而 Vue 2 是浅层的。
如https://v3.vuejs.org/api/basic-reactivity.html#markraw所述:
[...] the shallowXXX APIs below allow you to selectively opt-out of the default deep reactive/readonly conversion and embed raw, non-proxied objects in your state graph. They can be used for various reasons:
- Some values simply should not be made reactive, for example a complex 3rd party class instance, or a Vue component object.
...这是 Leaflet 构建 map
对象的情况。
一个非常简单的解决方法是不使用 this.map
(即不在组件状态中存储 Leaflet 构建的 map
对象,以防止 Vue 代理它),而只是存储它在本地(例如 const map = L.map()
然后是 myLayer.addTo(map)
)。
但是如果我们确实需要存储地图对象怎么办,通常是为了以后可以重新使用它,例如如果我们想在用户操作上添加一些层?
然后确保正确 unwrap / unproxy this.map
在与 Leaflet 一起使用之前,例如使用 Vue 3 toRaw
utility function:
Returns the raw, original object of a
reactive
orreadonly
proxy. This is an escape hatch that can be used to temporarily read without incurring proxy access/tracking overhead or write without triggering changes.
import { toRaw } from "vue";
export default {
name: "Map",
data() {
return {
map: null,
};
},
mounted() {
const map = L.map("map").setView([51.959, -8.623], 12);
L.tileLayer("https://{s}.tile.osm.org/{z}/{x}/{y}.png", {
attribution:
'© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);
L.circleMarker([51.959, -8.623])
.addTo(map)
.bindPopup("I am a marker")
.openPopup();
this.map = map;
},
methods: {
addCircleMarker() {
L.circleMarker([
51.959 + Math.random() * 0.05,
-8.623 + Math.random() * 0.1,
])
.addTo(toRaw(this.map)) // Make sure to "unproxy" the map before using it with Leaflet
.bindPopup("I am a marker")
.openPopup();
},
},
}
演示:https://codesandbox.io/s/priceless-colden-g7ju9?file=/src/components/HelloWorld.vue