Uncaught TypeError: this._map is null (Vue.js 3, Leaflet)

时间:2021-01-31 16:49:17

标签: vue.js leaflet vuejs3

我在 Vue.js 项目(版本 3)中收到来自 Leaflet 的奇怪错误。

如果我关闭弹出窗口并放大/缩小,Firefox 上会发生此错误:

<块引用>

未捕获的类型错误:this._map 为空

在 Chrome 上:

<块引用>

无法读取 null 的属性 '_latLngToNewLayerPoint'

地图组件如下:

<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: '&copy; <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>

如何重现错误:

  1. 打开stackblitz:https://stackblitz.com/edit/vue-gjeznj
  2. 关闭弹出窗口
  3. 放大/缩小

这可能只是一个错误吗?还是我遗漏了代码中的任何错误?

2 个答案:

答案 0 :(得分:1)

FWIW,这似乎是自 Vue 3 以来的新问题。

带有 Leaflet 的 Vue 版本 2 不存在此问题:https://codesandbox.io/s/fast-firefly-lqmwm?file=/src/components/HelloWorld.vue

为了确保,这里是使用相同代码但 Vue 版本 3 在 CodeSandbox 上重现的问题: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中所述:

<块引用>

[...] 下面的shallowXXX API 允许您有选择地退出默认的深度反应/只读转换,并在您的状态图中嵌入原始的、非代理的对象。它们可用于各种原因:

  • 某些值根本不应该是响应式的,例如复杂的 3rd 方类实例或 Vue 组件对象。

...这是 Leaflet 构建的 map 对象的情况。

一个非常简单的解决方法是不使用 this.map(即不将 Leaflet 构建的 map 对象存储在组件状态中,以防止 Vue 代理它),而只是将其存储在本地(例如 const map = L.map() 然后是 myLayer.addTo(map))。

但是如果我们确实需要存储地图对象怎么办,通常是为了我们以后可以重用它,例如如果我们想在用户操作上添加一些图层?

然后确保在将它与 Leaflet 一起使用之前正确unwrap / unproxy this.map,例如使用 Vue 3 toRaw utility function

<块引用>

返回 reactivereadonly 代理的原始原始对象。这是一个逃生舱,可用于临时读取而不会产生代理访问/跟踪开销或写入而不会触发更改。

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:
        '&copy; <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

答案 1 :(得分:0)

阅读 arieljuod 的链接后,似乎是唯一的选择,无需调整 Leaflet 的 js。文件,是禁用缩放动画。

this.map = L.map("map", {zoomAnimation: false})

如果需要动画,这里建议对 Leaflet 的 js 文件做一个小调整:https://salesforce.stackexchange.com/a/181000