苗条的传单

Svelte with leaflet

我正在尝试将 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 但想从头开始学习。

由于 Svelte 等框架的 not-really-straightforward 生命周期和 Leaflet 的 really-straightforward let-me-do-DOM-stuff 架构,这项看似简单的任务变得复杂。

有几种方法可以解决这个问题。我将描述一个,基于在 Leaflet 地图的 Svelte 组件中嵌套 Leaflet 层的 Svelte 组件,并使用 setContext and getContext to handle the Leaflet L.Map instance around. (I'm borrowing this technique from 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 生命周期的工作方式,children 组件将在 parent 组件之前获得 "rendered",但是 parent 组件需要一个 DOM 元素来创建 L.Map 实例(即地图容器)。所以这可能会延迟到 onRender Svelte 生命周期回调,但这会发生在 之后 开槽 children 被实例化并且它们的 onRender 生命周期回调是叫。所以等待 Svelte 实例化一个 DOM 元素来包含地图,然后 then 实例化 L.Mapthen 传递那个实例到上下文并然后在标记元素中获取上下文可能是一场噩梦。

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

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

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

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

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

<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 位的数据结构和一些命令式编程会更简单)。有时,理解同时工作的多个框架的生命周期含义可能 非常 令人困惑,而 非常 time-consuming 当出现错误时出现。