【问题标题】:Svelte with leaflet苗条的传单
【发布时间】:2020-06-14 15:02:31
【问题描述】:

我正在尝试结合传单找到进入 Svelte 的方法。我卡住的地方是如何正确地将传单组件拆分为文件。为了学习,我正在尝试用 svelte 构建官方的official leaflet quickstart

这就是我的 app.svelte 的样子:

<script>
  import L from 'leaflet';
  import { onMount } from "svelte";
  import { Circle } from "./components/Circle.svelte";

  let map;

  onMount(async () => {
    map = L.map("map");

    L.tileLayer("https://a.tile.openstreetmap.org/{z}/{x}/{y}.png ", {
      attribution:
        'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
      maxZoom: 18,
        tileSize: 512,
        zoomOffset: -1
    }).addTo(map);

    map.setView([51.505, -0.09], 13);
    Circle.addTo(map);

  });
</script>

<style>
    html,body {
        padding: 0;
        margin: 0;
    }
    html, body, #map {
        height: 100%;
        width: 100vw;
    }
</style>

<svelte:head>
    <link
    rel="stylesheet"
    href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
    integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
    crossorigin="" />
</svelte:head>

<div id="map" />

和我的圈子组件:

<script context="module">
    import L from 'leaflet';
    export let map_obj;

    export let Circle = L.circle([51.508, -0.11], {
        color: "red",
        fillColor: '#f03',
        fillOpacity: 0.5,
        radius: 500
    });
</script>

虽然这有效,但我认为考虑每个组件并使用Circle.addTo(map); 将其添加到地图中并不有效。如何将地图对象传递给圆形组件,或者是否有更好的模式来构建包含多个组件的地图?

注意:我知道svelte/leaflet,但喜欢从头开始学习。

【问题讨论】:

    标签: leaflet svelte


    【解决方案1】:

    由于 Svelte 等框架的生命周期并不十分简单,而 Leaflet 的 let-me-do-DOM-stuff 架构非常简单,因此这个看似简单的任务变得复杂。

    有几种方法可以解决这个问题。我将描述一个,基于将 Leaflet 层的 Svelte 组件嵌套在 Leaflet 映射的 Svelte 组件中,并使用 setContextgetContext 处理 Leaflet L.Map 实例。 (我从https://github.com/beyonk-adventures/svelte-mapbox借用了这个技术)

    所以L.Marker 的 Svelte 组件看起来像:

    <script>
        import L from 'leaflet';
        import { getContext } from "svelte";
    
        export let lat = 0;
        export let lng = 0;
    
        let map = getContext('leafletMapInstance');
    
        L.marker([lat, lng]).addTo(map);
    </script>
    

    很简单 - 通过 getContext 从 Svelte 上下文中获取 L.Map 实例,实例化 L.Marker,添加它。这意味着必须有一个用于设置上下文的地图的 Svelte 组件,这将需要用于插入的标记的组件,即

    <script>
        import LeafletMap from './LeafletMap.svelte'
        import LeafletMarker from './LeafletMarker.svelte'
    </script>
    
    <LeafletMap>
        <LeafletMarker lat=40 lng=-3></LeafletMarker>
        <LeafletMarker lat=60 lng=10></LeafletMarker>
    </LeafletMap>
    

    ...然后 Leaflet 地图的 Svelte 组件将创建 L.Map 实例,将其设置为上下文,然后就完成了,对吗?没那么快。这就是事情变得奇怪的地方。

    由于 Svelte 生命周期的工作原理,子组件将在父组件之前“渲染”,但父组件需要一个 DOM 元素来创建 L.Map 实例(即地图容器)。因此,这可能会延迟到onRender Svelte 生命周期回调,但这会发生在槽子被实例化并调用它们的onRender 生命周期回调之后。所以等待 Svelte 实例化一个包含地图的 DOM 元素,then 实例化 L.Mapthen 将该实例传递给上下文,then 在标记元素中获取上下文可能是一场噩梦。

    因此,解决此问题的一种方法是创建一个分离的 DOM 元素,在那里实例化一个 L.Map,即 ...

    let map = L.map(L.DomUtil.create('div')
    

    ...在上下文中设置它,即...

    import { setContext } from "svelte";
    setContext('leafletMapInstance', map);
    

    ...这将允许将由开槽组件实例化的 Leaflet 层添加到分离(因此不可见)的地图中。一旦所有生命周期的东西都让 L.Map 的 Svelte 组件有一个实际的 DOM 元素附加到 DOM,将地图容器附加到它,即在 Svelte 组件的 HTML 部分中有这个......

    <div class='map' bind:this={mapContainer}>
    

    ...一旦它实际附加到 DOM,将地图容器附加到它并设置其大小,即 ...

    let mapContainer;
    onMount(function() {
        mapContainer.appendChild(map.getContainer());
        map.getContainer().style.width = '100%';
        map.getContainer().style.height = '100%';
        map.invalidateSize();
    });
    

    所以这个 Leaflet L.Map 的整个 Svelte 组件看起来或多或少像...

    <script>
      import L from "leaflet";
      import { setContext, onMount } from "svelte";
    
      let mapContainer;
      let map = L.map(L.DomUtil.create("div"), {
        center: [0, 0],
        zoom: 0,
      });
      setContext("leafletMapInstance", map);
      console.log("map", map);
    
      L.tileLayer("https://a.tile.openstreetmap.org/{z}/{x}/{y}.png ", {
        attribution:
          'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
      }).addTo(map);
    
      onMount(() => {
        mapContainer.appendChild(map.getContainer());
        map.getContainer().style.width = "100%";
        map.getContainer().style.height = "100%";
        map.invalidateSize();
      });
    </script>
    <svelte:head>
      <link
        rel="stylesheet"
        href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
        integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
        crossorigin=""
      />
    </svelte:head>
    <style>
      .map {
        height: 100vh;
        width: 100vw;
      }
    </style>
    <div class="map" bind:this="{mapContainer}">
      <slot></slot>
    </div>
    

    查看working example here

    作为旁注,我会说在将 Leaflet 夹在另一个 JS 框架中之前应该三思而后行,并且三思而后行的架构(插槽组件似乎是最干净和最可扩展的,但可能是大数据结构Leaflet 位的一些命令式编程会更简单)。有时,了解同时工作的多个框架的生命周期影响可能非常令人困惑,并且在出现错误时非常耗时。

    【讨论】:

    • 非常好的答案,非常感谢@IvanSanchez!只有一个问题。标记未显示在您的示例中?
    • 这是 github.com/Leaflet/Leaflet/issues/4968 的副作用,当 Leaflet 代码进行捆绑时,该错误往往会出现。
    • 啊,正确,我确实看到了 404 。哇糟糕,这将花费我几个小时。再次感谢!
    • 真的很好@IvanSanchez!考虑到 Leaflet 的困难,与 Svelte 一起使用的推荐映射框架是什么?
    猜你喜欢
    • 2020-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-15
    • 1970-01-01
    • 2017-06-24
    • 2023-02-21
    • 2011-10-12
    相关资源
    最近更新 更多