2020-04-24

Svelte Tutorial 8: Stores (English & Indonesia)

English

Stores


Not all application state belongs inside your application's component hierarchy. Sometimes, you'll have values that need to be accessed by multiple unrelated components, or by a regular JavaScript module.

Writable Stores


In Svelte, we do this with stores. A store is simply an object with a subscribe method that allows interested parties to be notified whenever the store value changes. Below is an example where, count is a store, and we're setting count_value in the count.subscribe callback.

let count_value;

const unsubscribe = count.subscribe(value => {
  count_value = value;
});

This is the definition of count. It's a writable store, which means it has set and update methods in addition to subscribe.

import { writable } from 'svelte/store';
export const count = writable(0);

count can be updated or set like this:

function increment() { count.update(n => n + 1); }
function reset() { count.set(0); }

Full example can be seen here.


Auto-Subscriptions


The app in the previous example works, but there's a subtle bug — the unsubscribe function never gets called. If the component was instantiated and destroyed many times, this would result in a memory leak.

One way to fix it would be to use the onDestroy lifecycle hook:

<script>
  import { onDestroy } from 'svelte';
  import { count } from './stores.js';
  import Incrementer from './Incrementer.svelte';
  import Decrementer from './Decrementer.svelte';
  import Resetter from './Resetter.svelte';

  let count_value;
  const unsubscribe = count.subscribe(value => {
    count_value = value;
  });

  onDestroy(unsubscribe);
</script>

<h1>The count is {count_value}</h1>

It starts to get a bit boilerplatey though, especially if your component subscribes to multiple stores. Instead, Svelte has a trick up its sleeve — with autosubscribe means you can reference a store value by prefixing the store name with $ (you don't need to manually do onDestroy too, it's automatic):

<script>
  import { count } from './stores.js';
  import Incrementer from './Incrementer.svelte';
  import Decrementer from './Decrementer.svelte';
  import Resetter from './Resetter.svelte';
</script>

<h1>The count is {$count}</h1>

You can see the difference in first and second example, much simpler by using $. Auto-subscription only works with store variables that are declared (or imported) at the top-level scope of a component.

You're not limited to using $count inside the markup, either — you can use it anywhere in the <script> as well, such as in event handlers or reactive declarations.

Any name beginning with $ is assumed to refer to a store value. It's effectively a reserved character — Svelte will prevent you from declaring your own variables with a $ prefix.

Readable Stores


Not all stores should be writable by whoever has a reference to them. For example, you might have a store representing the mouse position or the user's geolocation, and it doesn't make sense to be able to set those values from 'outside'. For those cases, we have readable stores.

Create a stores.js file. The first argument to readable is an initial value, which can be null or undefined if you don't have one yet. The second argument is a start function that takes a set callback and returns a  stop function. The start function is called when the store gets its first subscriber; stop is called when the last subscriber unsubscribes.

// this is stores.js
import { readable } from 'svelte/store';

export const time = readable(new Date(), function start(set) { 
  // called at subscribe
  const interval = setInterval(() => {
    set(new Date());
  }, 1000);

  return function stop() { // called when unsubscribe
    clearInterval(interval);
  };
});

Then we can use time in App.svelte:

<script>
import { time } from './stores.js';

const formatter = new Intl.DateTimeFormat('en', {
hour12: true,
hour: 'numeric',
minute: '2-digit',
second: '2-digit'
});
</script>

<h1>The time is {formatter.format($time)}</h1>


Derived Stores


You can create a store whose value is based on the value of one or more other stores with derived. Building on our previous example, we can create a store that derives the time the page has been open:

export const elapsed = derived(
  time,
  $time => Math.round(($time - start) / 1000)
);

It's possible to derive a store from multiple inputs, and to explicitly set a value instead of returning it (which is useful for deriving values asynchronously). Consult the API reference for more information. Full example can be seen here.

Custom Stores


As long as an object correctly implements the subscribe method, it's a store. Beyond that, anything goes. It's very easy, therefore, to create custom stores with domain-specific logic.

For example, the count store from our earlier example could include incrementdecrement and reset methods and avoid exposing set and update:

function createCount() {
  const { subscribe, set, update } = writable(0);
  
  return {
    subscribe,
    increment: () => update(n => n + 1),
    decrement: () => update(n => n - 1),
    reset: () => set(0)
  };
}

And here is how we're gonna use it in main app:

<script> import { count } from './stores.js'; </script>

<h1>The count is {$count}</h1>
<button on:click={count.increment}>+</button>
<button on:click={count.decrement}>-</button>
<button on:click={count.reset}>reset</button>

Stores Binding


If a store is writable — i.e. it has a set method — you can bind to its value, just as you can bind to local component state. In this example we have a writable store name and a derived store greeting.

import { writable, derived } from 'svelte/store';

export const name = writable('world');
export const greeting = derived(
  name,
  $name => `Hello ${$name}!`
);

Changing the input value will now update name and all its dependents. We can also assign directly to store values inside a component. Add a <button> element:

<script> import { name, greeting } from './stores.js'; </script>

<h1>{$greeting}</h1>
<input bind:value={$name}>
<button on:click="{() => $name+='!'}">
  Add exclamation mark!
</button>

The $name+='!' assignment is equivalent to name.set($name+'!').




Indonesia

Stores


Tidak semua nilai dalam aplikasi berada di dalam hirarki komponen. Kadang, kita akan mempunyai suatu nilai yang perlu diakses oleh beberapa komponen yang tidak berhubungan, atau bisa juga oleh modul JavaScript biasa.

Writable Stores


Di svelte, kita bisa menggunakan store untuk keperluan ini. Store adalah suatu object dengan method subscribe yang memungkinkan bagian yang membutuhkan untuk mendapat notifikasi saat nilai dari store itu berubah. Di bawah ini adalah contoh store, count dan kita akan mengatur nilai dari count_value pada callback count.subscribe.

let count_value;

const unsubscribe = count.subscribe(value => {
  count_value = value;
});

Di bawah ini adalah definisi dari count. Yang merupakan writable store, berarti count memiliki method set dan update selain subscribe.

import { writable } from 'svelte/store';
export const count = writable(0);

ini contoh cara update atau set count:

function increment() { count.update(n => n + 1); }
function reset() { count.set(0); }

Contoh lengkap bisa dilihat di sini.

Auto-Subscriptions


Aplikasi di contoh sebelumnya akan berjalan, tapi ada sedikit kekurangan — fungsi unsubscribe tidak pernah dijalankan. Jika komponen itu akan di instantiate dan di destroy berulang kali, akan ada memori yang bocor.

Salah satu cara untuk memperbaikinya adalah dengan menggunakan pancingan lifecycle onDestroy :

<script>
  import { onDestroy } from 'svelte';
  import { count } from './stores.js';
  import Incrementer from './Incrementer.svelte';
  import Decrementer from './Decrementer.svelte';
  import Resetter from './Resetter.svelte';

  let count_value;
  const unsubscribe = count.subscribe(value => {
    count_value = value;
  });

  onDestroy(unsubscribe);
</script>

<h1>The count is {count_value}</h1>

Tapi programnya mulai terlihat sedikit ribet, terutama jika komponen itu melakukan subscribe ke banyak store. Maka, svelte sudah menyediakan satu kemudahan — dengan autosubscribe, yang berarti kita bisa langsung melakukan referensi ke suatu store dengan memberikan awalan nama store dengan $ (maka kita tidak perlu lagi melakukan unsubscribe di onDestroy):

<script>
  import { count } from './stores.js';
  import Incrementer from './Incrementer.svelte';
  import Decrementer from './Decrementer.svelte';
  import Resetter from './Resetter.svelte';
</script>

<h1>The count is {$count}</h1>

Kita bisa liat perbedaan dari contoh pertama dan kedua, akan jauh lebih mudah dengan menggunakan $. Auto subscription hanya bekerja dengan variabel store yang dideklarasikan (atau di import) pada bagian atas dari scope suatu komponen.

Kita juga tidak terbatas untuk menggunakan $count hanya di dalam markup, kita bisa gunakan di dalam  <script> juga, seperti di event handle atau deklarasi reactive.

Semua nama yang berawalan dengan $ akan dianggap mengacu pada nilai store. Maka karakter ini adalah karakter reserved  — Svelte akan mencegah kita deklarasi variabel dengan awalan  $.

Readable Stores


Tidak semua store perlu bisa writable. Sebagai contoh , kita bisa mempunyai store yang menyimpan posisi mouse, atau geolokasi, maka akan tidak masuk akal jika nilai store itu mau dirubah-rubah. Untuk kasus seperti itu ada readable store, store yang hanya readonly.

Pertama buat file stores.js . Argumen pertama untuk readable adalah inisialisasi awal, bisa berisi null atau undefined jika memang belum ada. Argumen kedua adalah fungsi start yang akan menerima callback set dan me return fungsi  stop. Fungsi start akan dijalankan ketika store mendapat subsriber pertamanya, dan stop akan dijalankan saat subscriber terakhir unsubscribe.

// this is stores.js
import { readable } from 'svelte/store';

export const time = readable(new Date(), function start(set) { 
  // called at subscribe
  const interval = setInterval(() => {
    set(new Date());
  }, 1000);

  return function stop() { // called when unsubscribe
    clearInterval(interval);
  };
});

Sesudah itu kita bisa menggunakan komponen time di dalam App.svelte:

<script>
import { time } from './stores.js';

const formatter = new Intl.DateTimeFormat('en', {
hour12: true,
hour: 'numeric',
minute: '2-digit',
second: '2-digit'
});
</script>

<h1>The time is {formatter.format($time)}</h1>

Derived Stores


Kita bisa membuat store yang nilainya berdasarkan dari nilai satu atau lebih store dengan derived. Melanjutkan dari contoh sebelumnya, kita bisa membuat store yang akan menghitung waktu berapa lama halaman aplikasi sudah dijalankan:

export const elapsed = derived(
  time,
  $time => Math.round(($time - start) / 1000)
);

Kita bisa men derive store dari berbagai input, dan secara eksplisit memberikan nilai daripada me return nya (biasanya digunakan untuk derive sesuatu secara asinkron). Cek  API reference untuk informasi lebih lanjut. Contoh lengkap bisa dilihat di sini.

Custom Stores


Selama suatu object menjalankan method subscribe dengan benar, maka dia adalah store. Maka dari itu kita bisa dengan mudah membuat store custom dengan logika yang spesifik untuk domain tertentu.

Sebagai contoh store count dari contoh sebelumnya bisa menambakan method incrementdecrement dan reset sehingga tidak perlu mengekspos set dan update pada saat penggunaan komponen:

function createCount() {
  const { subscribe, set, update } = writable(0);
  
  return {
    subscribe,
    increment: () => update(n => n + 1),
    decrement: () => update(n => n - 1),
    reset: () => set(0)
  };
}

Kemudian bisa digunakan di aplikasi utama seperti ini:

<script> import { count } from './stores.js'; </script>

<h1>The count is {$count}</h1>
<button on:click={count.increment}>+</button>
<button on:click={count.decrement}>-</button>
<button on:click={count.reset}>reset</button>

Stores Binding


Jika suatu store writable — mempunyai method set — kita bisa melakukan bind pada nilainya, seperti kita bisa melakukan bind pada state komponen lokal. Pada contoh berikut kita memiliki store writable  name dan derived store greeting.

import { writable, derived } from 'svelte/store';

export const name = writable('world');
export const greeting = derived(
  name,
  $name => `Hello ${$name}!`
);

Maka mengubah nilai input sekarang akan meng update name dan semua dependensinya. Kita juga bisa melakukan assign nilai secara langsung pada store di dalam komponen. Tambahkan elemen <button> :

<script> import { name, greeting } from './stores.js'; </script>

<h1>{$greeting}</h1>
<input bind:value={$name}>
<button on:click="{() => $name+='!'}">
  Add exclamation mark!
</button>

Proses $name+='!' akan menghasilkan sesuatu yang sama dengan name.set($name+'!').

No comments :

Post a Comment

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