2020-04-28

Svelte Tutorial 17: Module Context (English & Indonesia)

English


Sharing Code


In all the examples we've seen so far, the <script> block contains code that runs when each component instance is initialised. For the vast majority of components, that's all you'll ever need.

Very occasionally, you'll need to run some code outside of an individual component instance. For example, you can play all five of these audio players simultaneously; it would be better if playing one stopped all the others.

We can do that by declaring a <script context="module"> block. Code contained inside it will run once, when the module first evaluates, rather than when a component is instantiated. Place this at the top of AudioPlayer.svelte:

<script context="module">
  let current;
</script>

<script>
  export let src;
  export let title;
  export let composer;
  export let performer;

  let audio;
  let paused = true;

  function stopOthers() {
    if (current && current !== audio) current.pause();
    current = audio;
  }
</script>

<style>
  article { margin: 0 0 1em 0; max-width: 800px }
  h2, p { margin: 0 0 0.3em 0; }
  audio { width: 100%; margin: 0.5em 0 1em 0; }
  .playing { color: #ff3e00; }
</style>

<article class:playing={!paused}>
  <h2>{title}</h2>
  <p><strong>{composer}</strong> / performed by {performer}</p>

  <audio
    bind:this={audio}
    bind:paused
    on:play={stopOthers}
    controls
    {src}
  ></audio>
</article>

It's now possible for the components to 'talk' to each other without any state management, you just have to create <AudioPlayer> in App.svelte and provide desired properties, the current variable is now shared between all <AudioPlayer>:

<script>
  import AudioPlayer from './AudioPlayer.svelte';
</script>

<!-- https://musopen.org/music/9862-the-blue-danube-op-314/ -->
<AudioPlayer
  src="https://sveltejs.github.io/assets/music/strauss.mp3"
  title="The Blue Danube Waltz"
  composer="Johann Strauss"
  performer="European Archive"
/>

<!-- https://musopen.org/music/43775-the-planets-op-32/ -->
<AudioPlayer
  src="https://sveltejs.github.io/assets/music/holst.mp3"
  title="Mars, the Bringer of War"
  composer="Gustav Holst"
  performer="USAF Heritage of America Band"
/>

<!-- https://musopen.org/music/8010-3-gymnopedies/ -->
<AudioPlayer
  src="https://sveltejs.github.io/assets/music/satie.mp3"
  title="GymnopĂ©die no. 1"
  composer="Erik Satie"
  performer="Prodigal Procrastinator"
/>

<!-- https://musopen.org/music/2567-symphony-no-5-in-c-minor-op-67/ -->
<AudioPlayer
  src="https://sveltejs.github.io/assets/music/beethoven.mp3"
  title="Symphony no. 5 in Cm, Op. 67 - I. Allegro con brio"
  composer="Ludwig van Beethoven"
  performer="European Archive"
/>

<!-- https://musopen.org/music/43683-requiem-in-d-minor-k-626/ -->
<AudioPlayer
  src="https://sveltejs.github.io/assets/music/mozart.mp3"
  title="Requiem in D minor, K. 626 - III. Sequence - Lacrymosa"
  composer="Wolfgang Amadeus Mozart"
  performer="Markus Staab"
/>

Exports


Anything exported from a context="module" script block becomes an export from the module itself. If we export a stopAll function from AudioPlayer.svelte...

<script context="module">
  const elements = new Set();

  export function stopAll() {
    elements.forEach(element => {
      element.pause();
    });
  }
</script>

...we can then import it from App.svelte...

<script>
  import AudioPlayer, { stopAll } from './AudioPlayer.svelte';
</script>

...and use it in an event handler:

<button on:click={stopAll}>
stop all audio
</button>

You can't have a default export, because the component is the default export. For working example you can check here.

Debugging


Occasionally, it's useful to inspect a piece of data as it flows through your app.

One approach is to use console.log(...) inside your markup. If you want to pause execution, though, you can use the {@debug ...} tag with a comma-separated list of values you want to inspect:

{@debug user}

<h1>Hello {user.firstname}!</h1>

If you now open your devtools and start interacting with the <input> elements, you'll trigger the debugger as the value of user changes. For working example you can check here.



Indonesia


Sharing Code


Pada semua contoh yang sudah kita lihat sejauh ini, block <script> adalah tempat meletakkan program yang akan berjalan ketika masing-masing komponen sudah ter inisialisasi. Untuk kebanyakan komponen, itu sudah sesuai dengan kebutuhan kita.

Tapi terkadang, kita butuh menjalankan program di luar komponen itu sendiri. Sebagai contoh, kita bisa menjalankan 5 player pada contoh berikut bersamaan; tetapi akan lebih baik, jika kita menjalankan satu maka yang lain otomatis akan berhenti.

Kita bisa melakukan itu dengan mendeklarasikan blok <script context="module">. Program yang terdapat di dalamnya hanya akan berjalan sekali ketika modul pertama dijalankan, dan bukan ketika setiap komponen terbentuk. Letakkan di bagian atas AudioPlayer.svelte:

<script context="module">
  let current;
</script>

<script>
  export let src;
  export let title;
  export let composer;
  export let performer;

  let audio;
  let paused = true;

  function stopOthers() {
    if (current && current !== audio) current.pause();
    current = audio;
  }
</script>

<style>
  article { margin: 0 0 1em 0; max-width: 800px }
  h2, p { margin: 0 0 0.3em 0; }
  audio { width: 100%; margin: 0.5em 0 1em 0; }
  .playing { color: #ff3e00; }
</style>

<article class:playing={!paused}>
  <h2>{title}</h2>
  <p><strong>{composer}</strong> / performed by {performer}</p>

  <audio
    bind:this={audio}
    bind:paused
    on:play={stopOthers}
    controls
    {src}
  ></audio>
</article>

Sekarang sudah memungkinkan untuk setiap komponen saling ber'bicara' tanpa perlu menggunakan manajemen state lagi, kita hanya perlu membuat <AudioPlayer> di App.svelte dan ini akan memberikan properti yang kita butuhkan, yaitu variabel current sekarang sudah bisa digunakan pada semua <AudioPlayer>:

<script>
  import AudioPlayer from './AudioPlayer.svelte';
</script>

<!-- https://musopen.org/music/9862-the-blue-danube-op-314/ -->
<AudioPlayer
  src="https://sveltejs.github.io/assets/music/strauss.mp3"
  title="The Blue Danube Waltz"
  composer="Johann Strauss"
  performer="European Archive"
/>

<!-- https://musopen.org/music/43775-the-planets-op-32/ -->
<AudioPlayer
  src="https://sveltejs.github.io/assets/music/holst.mp3"
  title="Mars, the Bringer of War"
  composer="Gustav Holst"
  performer="USAF Heritage of America Band"
/>

<!-- https://musopen.org/music/8010-3-gymnopedies/ -->
<AudioPlayer
  src="https://sveltejs.github.io/assets/music/satie.mp3"
  title="GymnopĂ©die no. 1"
  composer="Erik Satie"
  performer="Prodigal Procrastinator"
/>

<!-- https://musopen.org/music/2567-symphony-no-5-in-c-minor-op-67/ -->
<AudioPlayer
  src="https://sveltejs.github.io/assets/music/beethoven.mp3"
  title="Symphony no. 5 in Cm, Op. 67 - I. Allegro con brio"
  composer="Ludwig van Beethoven"
  performer="European Archive"
/>

<!-- https://musopen.org/music/43683-requiem-in-d-minor-k-626/ -->
<AudioPlayer
  src="https://sveltejs.github.io/assets/music/mozart.mp3"
  title="Requiem in D minor, K. 626 - III. Sequence - Lacrymosa"
  composer="Wolfgang Amadeus Mozart"
  performer="Markus Staab"
/>

Exports


Apapun yang di export dari blok skrip context="module" akan menjadi export dari modul itu sendiri. Jika kita mengekspor fungsi stopAll dari AudioPlayer.svelte...

<script context="module">
  const elements = new Set();

  export function stopAll() {
    elements.forEach(element => {
      element.pause();
    });
  }
</script>

...kita bisa meng impor dari App.svelte...

<script>
  import AudioPlayer, { stopAll } from './AudioPlayer.svelte';
</script>

...dan menggunakannya pada handler even:

<button on:click={stopAll}>
stop all audio
</button>

Kita tidak bisa mempunyai ekspor default, karena komponen itu sendiri sudah merupakan default ekspor. Contoh lengkap bisa dilihat di sini.

Debugging


Terkadang, kita perlu mengecek data yang berjalan di dalam app kita.

Salah satu cara adalah dengan menggunakan console.log(...) dalam program. Akan tetapi jika kita ingin mem pause eksekusi program, kita bisa menggunakan tag {@debug ...} dengan mencantumkan nilai-nilai yang ingin kita cek, dipisahkan oleh tanda koma:

{@debug user}

<h1>Hello {user.firstname}!</h1>

Jika sekarang kita membuka devtools dan mulai berinteraksi dengan elemen <input>, maka debugger akan berjalan setiap nilai dari user berubah. Contoh lengkap bisa dilihat di sini.


Svelte Tutorial 16: Special Elements (English & Indonesia)

English


<svelte:self>


Svelte provides a variety of built-in elements. The first, <svelte:self>, allows a component to contain itself recursively.

It's useful for things like this folder tree view, where folders can contain other folders. In Folder.svelte we want to be able to do this...

{#if file.type === 'folder'}
  <Folder {...file}/>
{:else}
  <File {...file}/>
{/if}

...but that's impossible, because a file can't import itself. Instead, we use <svelte:self>:

{#if file.type === 'folder'}
  <svelte:self {...file}/>
{:else}
  <File {...file}/>
{/if}

For full interactive example, you can check here.

<svelte:component>


A component can change its category altogether with <svelte:component>. Instead of a sequence of if blocks...

{#if selected.color === 'red'} <RedThing/>
{:else if selected.color === 'green'} <GreenThing/>
{:else if selected.color === 'blue'} <BlueThing/>
{/if}

...we can have a single dynamic component:

<svelte:component this={selected.component}/>

The this value can be any component constructor, or a falsy value — if it's falsy, no component is rendered. Here is full example:

<script>
  import RedThing from './RedThing.svelte';
  import GreenThing from './GreenThing.svelte';
  import BlueThing from './BlueThing.svelte';

  const options = [
    { color: 'red',   component: RedThing   },
    { color: 'green', component: GreenThing },
    { color: 'blue',  component: BlueThing  },
  ];

  let selected = options[0];
</script>

<select bind:value={selected}>
  {#each options as option}
    <option value={option}>{option.color}</option>
  {/each}
</select>

<svelte:component this={selected.component}/>

This is example of component RedThing.svelte:

<style>
  strong { color: red; }
</style>

<strong>red thing</strong>

<svelte:window>


Just as you can add event listeners to any DOM element, you can add event listeners to the window object with <svelte:window>.

<script>
  let key;
  let keyCode;

  function handleKeydown(event) {
    key = event.key;
    keyCode = event.keyCode;
  }
</script>

<style>
  div {
    display: flex;
    height: 100%;
    align-items: center;
    justify-content: center;
    flex-direction: column;
  }

  kbd {
    background-color: #eee;
    border-radius: 4px;
    font-size: 6em;
    padding: 0.2em 0.5em;
    border-top: 5px solid rgba(255,255,255,0.5);
    border-left: 5px solid rgba(255,255,255,0.5);
    border-right: 5px solid rgba(0,0,0,0.2);
    border-bottom: 5px solid rgba(0,0,0,0.2);
    color: #555;
  }
</style>

<svelte:window on:keydown={handleKeydown}/>

<div style="text-align: center">
  {#if key}
    <kbd>{key === ' ' ? 'Space' : key}</kbd>
    <p>{keyCode}</p>
    {:else}
      <p>Focus this window and press any key</p>
    {/if}
</div>

<svelte:window> binding


We can also bind to certain properties of window, such as scrollY.

<svelte:window bind:scrollY={y}/>

The list of properties you can bind to is as follows:

  • innerWidth
  • innerHeight
  • outerWidth
  • outerHeight
  • scrollX
  • scrollY
  • online — an alias for window.navigator.onLine
All except scrollX and scrollY are readonly. You can see interactive example here.

<svelte:body>


Similar to  <svelte:window>, the <svelte:body> element allows you to listen for events that fire on document.body. This is useful with the mouseenter and mouseleave events, which don't fire on window.

For example you can add the mouseenter and mouseleave handlers to the <svelte:body> tag:

<svelte:body
  on:mouseenter={handleMouseenter}
  on:mouseleave={handleMouseleave}
/>

You can see interactive example here.

<svelte:head>


The <svelte:head> element allows you to insert elements inside the <head> of your document:

<svelte:head>
  <link rel="stylesheet" href="tutorial/dark-theme.css">
</svelte:head>

<h1>Hello world!</h1>

In server-side rendering (SSR) mode, contents of <svelte:head> are returned separately from the rest of your HTML.

<svelte:options>


Lastly, <svelte:options> allows you to specify compiler options.

We'll use the immutable option as an example. In this app, the <Todo> component flashes whenever it receives new data. Clicking on one of the items toggles its done state by creating an updated todos array. This causes the other <Todo> items to flash, even though they don't end up making any changes to the DOM.

We can optimise this by telling the <Todo> component to expect immutable data. This means that we're promising never to mutate the todo prop, but will instead create new todo objects whenever things change. This is content of Todo.svelte:

<svelte:options immutable={true}/>

<script>
  import { afterUpdate } from 'svelte';
  import flash from './flash.js';

  export let todo;
  let div;

  afterUpdate( () => { flash(div); } );
</script>

<style>
  div {
    cursor: pointer;
    line-height: 1.5;
  }
</style>

<!-- the text will flash red whenever
     the `todo` object changes -->
<div bind:this={div} on:click>
  {todo.done ? 'đź‘Ť': ''} {todo.text}
</div>

This is flash.js:

export default function flash(element) {
  requestAnimationFrame(() => {
    element.style.transition = 'none';
    element.style.color = 'rgba(255,62,0,1)';
    element.style.backgroundColor = 'rgba(255,62,0,0.2)';

    setTimeout(() => {
      element.style.transition = 'color 1s, background 1s';
      element.style.color = '';
      element.style.backgroundColor = '';
    });
  });
}

And, this is App.svelte:

<script>
  import Todo from './Todo.svelte';

  let todos = [
    { id: 1, done: true, text: 'wash the car' },
    { id: 2, done: false, text: 'take the dog for a walk' },
    { id: 3, done: false, text: 'mow the lawn' }
  ];

  function toggle(toggled) {
    todos = todos.map(todo => {
      if (todo === toggled) {
        // return a new object
        return {
          id: todo.id,
          text: todo.text, 
          done: !todo.done
        };
      }

      // return the same object
      return todo;
    });
  }
</script>

<h2>Todos</h2>
{#each todos as todo}
  <Todo {todo} on:click={() => toggle(todo)}/>
{/each}

Now, when you toggle todos by clicking on them, only the updated component flashes.

The options that can be set here are:

  • immutable = {true} — you never use mutable data, so the compiler can do simple referential equality checks to determine if values have changed
  • immutable = {false} — the default. Svelte will be more conservative about whether or not mutable objects have changed
  • accessors = {true} — adds getters and setters for the component's props
  • accessors = {false} — the default
  • namespace = "..." — the namespace where this component will be used, most commonly "svg"
  • ag = "..." — the name to use when compiling this component as a custom element
Consult the API reference for more information on these options.




Indonesia


<svelte:self>


Svelte menyediakan bermacam-macam elemen built-in. Yang pertama,  <svelte:self>, memungkinkan sebuah komponen untuk membentuk diri sendiri di dalam dirinya secara rekursif.

Komponen ini berguna untuk aplikasi tampilan folder dalam bentuk tree, di mana dalam folder bisa terdapat folder lagi. Pada Folder.svelte kita ingin melakukan seperti ini...

{#if file.type === 'folder'}
  <Folder {...file}/>
{:else}
  <File {...file}/>
{/if}

...tapi itu tidak mungkin, karena sebuah file tidak bisa mengimpor dirinya sendiri. Maka kita akan menggunakan <svelte:self>:

{#if file.type === 'folder'}
  <svelte:self {...file}/>
{:else}
  <File {...file}/>
{/if}

Contoh lengkap bisa dilihat di sini.

<svelte:component>


Sebuah komponen bisa mengubah kategorinya sekaligus dengan <svelte:component>. Daripada menggunakan sekumpulan blok if...

{#if selected.color === 'red'} <RedThing/>
{:else if selected.color === 'green'} <GreenThing/>
{:else if selected.color === 'blue'} <BlueThing/>
{/if}

...kita bisa menggunakan sebuah komponen yang dinamis:

<svelte:component this={selected.component}/>

Isi dari this bisa berupa constructor komponen apapun, atau suatu nilai salah — Jika bernilai salah, maka tidak ada komponen yang dibentuk. Berikut adalah contoh lengkap:

<script>
  import RedThing from './RedThing.svelte';
  import GreenThing from './GreenThing.svelte';
  import BlueThing from './BlueThing.svelte';

  const options = [
    { color: 'red',   component: RedThing   },
    { color: 'green', component: GreenThing },
    { color: 'blue',  component: BlueThing  },
  ];

  let selected = options[0];
</script>

<select bind:value={selected}>
  {#each options as option}
    <option value={option}>{option.color}</option>
  {/each}
</select>

<svelte:component this={selected.component}/>

Ini adalah contoh isi dari RedThing.svelte:

<style>
  strong { color: red; }
</style>

<strong>red thing</strong>

<svelte:window>


Sama halnya dengan kita bisa menambahkan listener even pada elemen DOM apapun, kita juga bisa menambahkan listener even pada object window dengan <svelte:window>.

<script>
  let key;
  let keyCode;

  function handleKeydown(event) {
    key = event.key;
    keyCode = event.keyCode;
  }
</script>

<style>
  div {
    display: flex;
    height: 100%;
    align-items: center;
    justify-content: center;
    flex-direction: column;
  }

  kbd {
    background-color: #eee;
    border-radius: 4px;
    font-size: 6em;
    padding: 0.2em 0.5em;
    border-top: 5px solid rgba(255,255,255,0.5);
    border-left: 5px solid rgba(255,255,255,0.5);
    border-right: 5px solid rgba(0,0,0,0.2);
    border-bottom: 5px solid rgba(0,0,0,0.2);
    color: #555;
  }
</style>

<svelte:window on:keydown={handleKeydown}/>

<div style="text-align: center">
  {#if key}
    <kbd>{key === ' ' ? 'Space' : key}</kbd>
    <p>{keyCode}</p>
    {:else}
      <p>Focus this window and press any key</p>
    {/if}
</div>

<svelte:window> binding


Kita juga bisa menambahkan bind pada properti tertentu dari window, contohnya scrollY.

<svelte:window bind:scrollY={y}/>

Daftar properti yang bisa di bind adalah sebagai berikut:
  • innerWidth
  • innerHeight
  • outerWidth
  • outerHeight
  • scrollX
  • scrollY
  • online — sebuah alias untuk window.navigator.onLine
Semuanya kecuali scrollX dan scrollY adalah readonly. Contoh lengkap bisa dilihat di sini.

<svelte:body>


Mirip dengan <svelte:window>, elemen <svelte:body> memungkinkan kita untuk bisa menangkap even yang berjalan di document.body. Hal ini berguna untuk even mouseenter dan mouseleave, yang tidak berjalan di window.

Sebagai contoh kita bisa menambahkan handler mouseenter dan mouseleave ke tag <svelte:body>:

<svelte:body
  on:mouseenter={handleMouseenter}
  on:mouseleave={handleMouseleave}
/>

Contoh lengkap bisa dilihat di sini.

<svelte:head>


Elemen <svelte:head> memungkinkan kita untuk memasukan elemen di dalam <head> dari dokumen kita:

<svelte:head>
  <link rel="stylesheet" href="tutorial/dark-theme.css">
</svelte:head>

<h1>Hello world!</h1>

Pada mode server-side rendering (SSR), isi dari <svelte:head> akan di return secara terpisah dari keseluruhan HTML kita.

<svelte:options>


Terakhir, <svelte:options> memungkinkan kita untuk mengatur option dari compiler.

Kita akan menggunakan option immutable sebagai contoh. Pada app berikut, komponen <Todo> akan berkedip kapanpun ada data baru yang masuk. Mengklik pada salah satu item akan mengubah status done nya dengan membuat array todos yang sudah diupdate. Hal ini menyebabkan item <Todo> yang lain juga berkedip meskipun tidak terjadi perubahan apapun pada DOMnya.

Kita bisa memperbaiki ini dengan menginformasikan pada komponen <Todo> untuk menganggap data immutable. Ini berarti kita berjanji untuk tidak mengubah properti todo, tapi akan membuat object todo baru jika ada perubahan yang terjadi. Ini adalah isi dari Todo.svelte:

<svelte:options immutable={true}/>

<script>
  import { afterUpdate } from 'svelte';
  import flash from './flash.js';

  export let todo;
  let div;

  afterUpdate( () => { flash(div); } );
</script>

<style>
  div {
    cursor: pointer;
    line-height: 1.5;
  }
</style>

<!-- the text will flash red whenever
     the `todo` object changes -->
<div bind:this={div} on:click>
  {todo.done ? 'đź‘Ť': ''} {todo.text}
</div>

Ini adalah flash.js:

export default function flash(element) {
  requestAnimationFrame(() => {
    element.style.transition = 'none';
    element.style.color = 'rgba(255,62,0,1)';
    element.style.backgroundColor = 'rgba(255,62,0,0.2)';

    setTimeout(() => {
      element.style.transition = 'color 1s, background 1s';
      element.style.color = '';
      element.style.backgroundColor = '';
    });
  });
}

Dan ini adalah App.svelte:

<script>
  import Todo from './Todo.svelte';

  let todos = [
    { id: 1, done: true, text: 'wash the car' },
    { id: 2, done: false, text: 'take the dog for a walk' },
    { id: 3, done: false, text: 'mow the lawn' }
  ];

  function toggle(toggled) {
    todos = todos.map(todo => {
      if (todo === toggled) {
        // return a new object
        return {
          id: todo.id,
          text: todo.text, 
          done: !todo.done
        };
      }

      // return the same object
      return todo;
    });
  }
</script>

<h2>Todos</h2>
{#each todos as todo}
  <Todo {todo} on:click={() => toggle(todo)}/>
{/each}

Sekarang, ketika kita men toggle todos dengan mengklik, hanya komponen terupdate saja yang akan berkedip.

Option lain yang bisa diatur adalah:

  • immutable = {true} — kita tidak pernah menggunakan data yang bisa mutate, jadi compiler akan melakukan pengecekan persamaan referensi secara sederhana untuk menentukan apakah ada nilai yang berubah
  • immutable = {false} — nilai default. Svelte akan lebih konservatif dalam menentukan object terjadi perubahan atau tidak
  • accessors = {true} — menambahkan getter dan setter pada properti komponen
  • accessors = {false} — nilai default
  • namespace = "..." — namespace di mana komponen ini akan digunakan, paling umum adalah "svg"
  • ag = "..." — nama yang akan digunakan ketika meng compile komponen sebagai elemen kustom
Lihat pada API reference untuk informasi lebih lanjut pada option.

2020-04-27

Svelte Tutorial 15: Context API (English & Indonesia)

English

Context API


The context API provides a mechanism for components to 'talk' to each other without passing around data and functions as props, or dispatching lots of events. It's an advanced feature, but a useful one.

Take this example app using a Mapbox GL map. We'd like to display the markers, using the <MapMarker> component, but we don't want to have to pass around a reference to the underlying Mapbox instance as a prop on each component. Full example can be seen here.

setContext and getContext


There are two halves to the context API — setContext and getContext. If a component calls setContext(key, context), then any child component can retrieve the context with const context = getContext(key).

Let's set the context first. In Map.svelte, import setContext from svelte and key from mapbox.js and call setContext:

<script> // this is Map.svelte
  import { onMount, setContext } from 'svelte';
  import { mapbox, key } from './mapbox.js';

  // set the context here...
  setContext(key,{ getMap: () => map });

  export let lat;
  export let lon;
  export let zoom;

  let container;
  let map;

  onMount(() => {
    const link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = 'https://unpkg.com/mapbox-gl/dist/mapbox-gl.css';

    link.onload = () => {
      map = new mapbox.Map({
        container,
        style: 'mapbox://styles/mapbox/streets-v9',
        center: [lon, lat],
        zoom
      });
    };

    document.head.appendChild(link);

    return () => {
      map.remove();
      link.parentNode.removeChild(link);
    };
  });
</script>

<style>
  div { width: 100%; height: 100%; }
</style>

<div bind:this={container}>
  {#if map} <slot></slot> {/if}
</div>

The context object can be anything you like. Like lifecycle functionsetContext and getContext must be called during component initialisation; since map isn't created until the component has mounted, our context object contains a getMap function rather than map itself.

On the other side of the equation, in MapMarker.svelte, we can now get a reference to the Mapbox instance:

<script>
  import { getContext } from 'svelte';
  import { mapbox, key } from './mapbox.js';
  
  // ...get the context here
  const { getMap } = getContext(key);
  const map = getMap();

  export let lat;
  export let lon;
  export let label;

  const popup = new mapbox.Popup({ offset: 25 })
    .setText(label);

  const marker = new mapbox.Marker()
    .setLngLat([lon, lat])
    .setPopup(popup)
    .addTo(map);
</script>

The markers can now add themselves to the map. Here is example of App.svelte, that use Map and MapMarker component.

<script>  import Map from './Map.svelte';  import MapMarker from './MapMarker.svelte';</script>

<Map lat={35} lon={-84} zoom={3.5}>
  <MapMarker lat={37.8225} lon={-122.0024} 
    label="Svelte Body Shaping"/>
  <MapMarker lat={33.8981} lon={-118.4169} 
    label="Svelte Barbershop & Essentials"/>
  <MapMarker lat={29.7230} lon={-95.4189} 
    label="Svelte Waxing Studio"/>
  <MapMarker lat={28.3378} lon={-81.3966} 
    label="Svelte 30 Nutritional Consultants"/>
  <MapMarker lat={40.6483} lon={-74.0237} 
    label="Svelte Brands LLC"/>
  <MapMarker lat={40.6986} lon={-74.4100} 
    label="Svelte Medical Systems"/>
</Map>

Context keys


In mapbox.js you'll see this line:

const key = {};

We can use anything as a key — we could do setContext('mapbox',...)for example. The downside of using a string is that different component libraries might accidentally use the same one; using an object literal means the keys are guaranteed not to conflict in any circumstance (since an object only has referential equality to itself, i.e. {} !== {} whereas "x" ==="x"), even when you have multiple different contexts operating across many component layers. This is full content of mapbox.js:

import mapbox from 'mapbox-gl';
// https://docs.mapbox.com/help/glossary/access-token/
mapbox.accessToken = MAPBOX_ACCESS_TOKEN;
const key = {};
export { mapbox, key };



Contexts vs. stores


Contexts and stores seem similar. They differ in that stores are available to any part of an app, while a context is only available to a component and its descendants. This can be helpful if you want to use several instances of a component without the state of one interfering with the state of the others.

In fact, you might use the two together. Since context is not reactive, values that change over time should be represented as stores:

const { these, are, stores } = getContext(...);



Indonesia


Context API


Context API memberikan mekanisme bagi komponen untuk 'bicara' satu sama lain tanpa perlu mem pass kan data dan fungsi sebagai properti, atau melakukan banyak dispatch even. Ini adalah fitur tingkat tinggi, yang sangat berguna.

Sebagai contoh ini adalah app yang menggunakan peta Mapbox GL. Kita ingin menampilkan marker (=penanda) pada peta, menggunakan komponen <MapMarker>, tapi kita tidak ingin harus repot-repot mem pass kan referensi ke Mapbox sebagai properti pada masing-masing komponen. Contoh lengkap bisa dilihat di sini.

setContext and getContext


Ada 2 bagian dari context API — setContext dan getContext. Jika suatu komponen memanggil setContext(key, context), maka setiap komponen anaknya akan bisa mengakses context dengan const context = getContext(key).

Pertama kita perlu mengeset context dulu di Map.svelte, impor setContext dari svelte dan key dari mapbox.js dan panggil setContext:

<script> // this is Map.svelte
  import { onMount, setContext } from 'svelte';
  import { mapbox, key } from './mapbox.js';

  // set the context here...
  setContext(key,{ getMap: () => map });

  export let lat;
  export let lon;
  export let zoom;

  let container;
  let map;

  onMount(() => {
    const link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = 'https://unpkg.com/mapbox-gl/dist/mapbox-gl.css';

    link.onload = () => {
      map = new mapbox.Map({
        container,
        style: 'mapbox://styles/mapbox/streets-v9',
        center: [lon, lat],
        zoom
      });
    };

    document.head.appendChild(link);

    return () => {
      map.remove();
      link.parentNode.removeChild(link);
    };
  });
</script>

<style>
  div { width: 100%; height: 100%; }
</style>

<div bind:this={container}>
  {#if map} <slot></slot> {/if}
</div>

Object dari context bisa berupa apapun yang kita suka. Seperti fungsi lifecyclesetContext dan getContext harus dipanggil saat inisialisasi komponen; karena map tidak akan dibentuk sampai komponen sudah di mount, maka context object kita akan berisi fungsi getMap dan bukan map.

Sementara di sisi lain, pada MapMarker.svelte, kita sekarang bisa mendapatkan referensi ke  Mapbox:

<script>
  import { getContext } from 'svelte';
  import { mapbox, key } from './mapbox.js';
  
  // ...get the context here
  const { getMap } = getContext(key);
  const map = getMap();

  export let lat;
  export let lon;
  export let label;

  const popup = new mapbox.Popup({ offset: 25 })
    .setText(label);

  const marker = new mapbox.Marker()
    .setLngLat([lon, lat])
    .setPopup(popup)
    .addTo(map);
</script>


Marker sekarang sudah bisa ditambahkan ke peta. Berikut adalah contoh dari App.svelte, yang menggunakan komponen Map dan MapMarker.

<script>
  import Map from './Map.svelte';

  import MapMarker from './MapMarker.svelte';

</script>

<Map lat={35} lon={-84} zoom={3.5}>
  <MapMarker lat={37.8225} lon={-122.0024} 
    label="Svelte Body Shaping"/>
  <MapMarker lat={33.8981} lon={-118.4169} 
    label="Svelte Barbershop & Essentials"/>
  <MapMarker lat={29.7230} lon={-95.4189} 
    label="Svelte Waxing Studio"/>
  <MapMarker lat={28.3378} lon={-81.3966} 
    label="Svelte 30 Nutritional Consultants"/>
  <MapMarker lat={40.6483} lon={-74.0237} 
    label="Svelte Brands LLC"/>
  <MapMarker lat={40.6986} lon={-74.4100} 
    label="Svelte Medical Systems"/>
</Map>


Context keys


Pada mapbox.js kita akan melihat baris ini:

const key = {};


Kita bisa menggunakan apapun sebagai kunci — contohnya kita bisa menggunakan setContext('mapbox',...). Tapi kekurangan dari menggunakan string sebagai kunci adalah library komponen yang berbeda bisa saja secara kebetulan menggunakan string yang sama; menggunakan object literal berarti kunci yang digunakan tidak akan mengalami konflik pada situasi apapun (karena sebuah object hanya akan dianggap sama dengan dirinya sendiri, contoh {} !== {} sementara "x" ==="x"), bahkan ketika kita mempunyai beberapa context yang berbeda pada banyak tingkatan komponen. Ini adalah isi lengkat dari mapbox.js:

import mapbox from 'mapbox-gl';
// https://docs.mapbox.com/help/glossary/access-token/
mapbox.accessToken = MAPBOX_ACCESS_TOKEN;
const key = {};
export { mapbox, key };


Contexts vs. stores


Context dan store terlihat mirip. Perbedaannya adalah store bisa digunakan pada semua bagian dari app, sementara context hanya tersedia untuk komponen itu dan turunannya. Hal ini bisa bermanfaat jika kita ingin menggunakan beberapa komponen tanpa ada state nya yang saling campur aduk dengan state dari komponen lain.

Faktanya, kita bisa menggunakan context dan store bersamaan. Tapi satu hal yang harus diingat karena context tidak reactive, nilai yang akan berubah-ubah sebaiknya disimpan pada store:

const { these, are, stores } = getContext(...);