Web/JavaScript

[Svelte] Store

llHoYall 2021. 11. 13. 23:12

Svelte has a store, so we don't need to use 3rd party modules.

Writable Store

A writable store is the most general store that can be read and written.

Simple Example

First, I will make a store.

// store.ts
import { writable } from 'svelte/store';
import type { Writable } from 'svelte/store';

export const count: Writable<number> = writable(0);

writable() function returns writable store object.

 

Next, make a component.

<!-- WritableStore.svelte -->
<script lang="ts">
  import { count } from '../store';

  let number;

  count.subscribe((c) => {
    number = c;
  });

  const increase = () => {
    count.update((c) => c + 1);
  };
</script>

<div>{number}</div>
<button on:click={increase}>+</button>

subscribe() method is called when the value of the store object is changed.

update() method takes the current value and returns the updated value.

 

Now, use it.

<!-- index.svelte -->
<script lang="ts">
  import WritableStore from './WritableStore.svelte';

  let toggle = true;
</script>

<button on:click={() => (toggle = !toggle)}>Toggle</button>

{#if toggle}
  <WritableStore />
{/if}

Unsubscription

// store.ts
export let count: Writable<number> = writable(0, () => {
  console.log('There are subscribers');
  return () => console.log('There is no subscriber');
});

writable() function can have a second argument as a function.

That function is called when the store object is subscribed.

That function can return a function and the returned function is called when there is no subscriber anymore.

<!-- WritableStore.svelte -->
<script lang="ts">
  import { onDestroy } from 'svelte';

  const unsubscribeCount = count.subscribe((c) => {
    number = c;
  });
  
  onDestroy(() => unsubscribeCount());
</script>

If this component is unmounted, the count object will be unsubscribed.

Set Value to Writable Store Object

Let's make one more store object.

// store.ts
export const name: Writable<string> = writable('HoYa');
<!-- WritableStore.svelte -->
<script lang="ts">
  import { count, name } from '../store';

  let userName;

  name.subscribe((n) => {
    userName = n;
  });

  const changeName = () => {
    name.set('Kim');
  };

  onDestroy(() => unsubscribeCount());
</script>

<div>{number}</div>
<div>{userName}</div>
<button on:click={increase} on:click={changeName}>Update</button>

set() method sets the value to the writable store object.

I reused the button because I want to show you that it is able to have multiple listeners.

Automatic Subscription

BUT!! Svelte provides a more convenient way.

You will only use the previous way if it is not Svelte.

<!-- WritableStore.svelte -->
<script lang="ts">
  import { count, name } from '../store';

  const increase = () => {
    $count += 1;
  };

  const changeName = () => {
    $name = 'Kim';
  };
</script>

<div>{$count}</div>
<div>{$name}</div>
<button on:click={increase} on:click={changeName}>Update</button>

TaDa~ How cool~!!

The only thing you need is to use $.

Then, svelte will be done automatically subscribing, updating, and unsubscribing.

Readable Store

A readable store is a read-only store.

Simple Example

Let's create a readable store like a writable store.

// store.ts
import { readable } from 'svelte/store';
import type { Readable } from 'svelte/store';

interface UserData {
  name: string;
  age: number;
  male: boolean;
}

const userData: UserData = {
  name: 'HoYa',
  age: 18,
  male: true
};

export const user: Readable<UserData> = readable(userData);

Readable store only supports subscribe() method.

And, we can use an automatic subscription using $.

 

<!-- ReadableStore.svelte -->
<script lang="ts">
  import { user } from '../store';
</script>

<div>{$user.name}</div>

The value of a readable store object is not able to be modified.

We can only read it.

Unsubscription

Unsubscription of a readable store is the same as writable store

// store.ts
export const user: Readable<UserData> = readable(userData, () => {
  console.log('There are subscribers');
  return () => console.log('There is no subscriber');
});

Set Initial Value of Readable Store

// store.ts
export const user: Readable<UserData> = readable(userData, (set) => {
  console.log('There are subscribers');
  delete userData.age;
  set(userData);
  return () => console.log('There is no subscriber');
});

A readable() function can have a second argument as a function with argument.

This argument is a function and it can set an initial value once when there is a subscriber.

I removed age data from userData, and it is set as initial data.

So, there is no age data in this readable store object.

Derived Store

A derived store is to create a new store using one or more store values.

In addition, it is a read-only store like a readable store.

Simple Example

// store.ts
import { writable, derived } from 'svelte/store';
import type { Writable } from 'svelte/store';

export const count: Writable<number> = writable(0);

export const double = derived(count, ($count) => $count * 2);

I created a derived store using a writable store.

<!-- DerivedStore.svelte -->
<script lang="ts">
  import { count, double } from '../store';
</script>

<div>{$count}</div>
<div>{$double}</div>

Nothing special. You can easily understand.

Derived Store from Multiple Store

// store.ts
export const total = derived([count, double], ([$count, $double]) => $count + $double);

We can give multiple stores as an array form.

<!-- DerivedStore.svelte -->
<script lang="ts">
  import { count, double, total } from '../store';
</script>

<div>{$count} + {$double} = {$total}</div>

Unsubscription & Set Value

As I earlier said, derived store is the same as readable store.

So, we can use the same thing in the second argument.

// store.ts
export const total = derived([count, double], ([$count, $double], set) => {
  console.log('There are subscribers');
  set($count + $double);
  return () => console.log('There is no subscriber');
});

We can set the value of the derived store object.

And, a derived store is executed whenever one of the related stores is changed.

There is a little different thing in the derived store.

Every time it is executed, it is initialized and re-subscribed to all subscribers.

It initializes all subscribers every time it is executed and is re-subscribed.

Initial Value

A derived store can have an initial value for a task taking time.

// store.ts
export const delayedIncrease = derived(
  count,
  ($count, set) => {
    setTimeout(() => set(($count + 1).toString()), 1000);
  },
  'Calculating...'
);

The third argument is used the first time this store is used.

Get Value from Store

If we just want to get a value once from stores, the subscription is a burden.

In this case, we can use get() function.

<!-- index.svelte -->
<script lang="ts">
  import { get } from 'svelte/store';
  import { count, user, double } from '../store';

  console.log(get(count));	// Writable Store
  console.log(get(user));	// Readable Store
  console.log(get(double));	// Derived Store
</script>

It is easy to use.

Custom Store

We can make our own store because a store just needs subscribe() method.

// store.ts
import { writable } from 'svelte/store';

const { subscribe, set, update } = writable(0);

export const customData = {
  subscribe,
  set,
  update,
  increase: (): void => update((n) => n + 1),
  decrease: (): void => update((n) => n - 1),
  reset: (): void => set(0)
};

I created a writable store.

<!-- CustomStore.svelte -->
<script lang="ts">
  import { customData } from '../store';
</script>

<h1>Custom Store</h1>
<div>{$customData}</div>
<button on:click={customData.increase}>+</button>
<button on:click={customData.decrease}>-</button>
<button on:click={customData.reset}>Reset</button>

We can subscribe to our custom store and use our store methods.

We can make other types of stores as well.

Conclusion

Svelte's store is easy to understand and easy to use.

It is a very important thing in the point of data storage and management.

Make it to yours.