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(...);

No comments:

Post a Comment

THINK: is it True? is it Helpful? is it Inspiring? is it Necessary? is it Kind?