Svelte ] 스벨트에서의 전역상태관리 Store

히징·2023년 2월 14일
0

Stores

svelte에는 전역 상태를 저장하는 store가 계층구조에 포함되어있어 유용하게 사용할 수 있습니다. 그러나 모든 어플리케이션이 상태가 어플리케이션 계층 구조에 속하는 것은 아닙니다. 상태 관리를 위해서 자바스크립트 모듈 등을 사용해야 하는 경우도 많습니다. ( redux, recoil … )

스벨트에서는 상태관리를 stores를 통해 관리합니다. store는 값이 변경될 때 마다 subscribe 메소드를 사용하는 간단한 객체입니다.

1. Writable stores

import { writable } from 'svelte/store';

export const count = writable(0);
	import { count } from './stores.js';

	function reset() {
		count.set(0);
	}
</script>

<button on:click={reset}>
	reset
</button>
	import { count } from './stores.js';

	function increment() {
		count.update(n => n + 1);
	}
</script>

<button on:click={increment}>
	+
</button>
	import { count } from './stores.js';

	function decrement() {
		count.update(n => n - 1);
	}
</script>

<button on:click={decrement}>
	-
</button>
	import { count } from './stores.js';
	import Incrementer from './Incrementer.svelte';
	import Decrementer from './Decrementer.svelte';
	import Resetter from './Resetter.svelte';

	let countValue;

	count.subscribe(value => {
		countValue = value;
	});
</script>

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

<Incrementer/>
<Decrementer/>
<Resetter/>

2. Auto-subscriptions

이전의 예제는 스토어가 구독되지만 구독 취소는 되지 않았습니다.

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

subscribe 메소드를 호출하면 unsubscribe 함수가 반환됩니다.

이제 unsubscribe를 선언할 수 있으며 onDestroy lifecycle hook을 통해 호출할 수 있습니다.

  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 countValue;

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

	onDestroy(unsubscribe);
</script><h1>The count is {countValue}</h1>

svelte에서는 컴포넌트가 여러 스토어를 구독하는 경우 등을 위해서 자동 구독을 지원합니다. 구독하는 변수 앞에 $ 를 붙여 사용할 수 있습니다.

	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>

자동 구독은 컴포넌트의 최상위 스코프에서 선언된 store 변수에서만 작동합니다.

$count 는 위 예제와 같이 마크업 내에서만 사용 가능한 것이 아닌, 이벤트 핸들러나 반응성 선언과 같이 어디에서나 사용 가능합니다.

$ 가 앞에 붙어있는 변수는 모두 store 값을 참조하는 값입니다. 따라서 Svelte에서는 $로 시작하는 변수명을 짓는 것은 피해야 합니다.

3. Readable Stores

만약 마우스 위치나 사용자의 지리적 위치 등을 나타내는 store 가 있다면 외부에서 해당 스토어의 값을 설정하지 못합니다. 이러한 경우을 위하여 읽기만 가능한 store를 지정할 수 있습니다.

import { readable } from 'svelte/store';

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

	return function stop() {
		clearInterval(interval);
	};
});

readable에 대한 첫번째 인자는 초기 값을 지정해줍니다. 아직 값이 없는 경우라면 null값이나 undefined 값이 될 수도 있습니다. 두번째로는 set을 콜백으로 받고 stop 함수를 리턴하는 start 함수를 입니다.

store에서 첫번째 구독자를 얻을 때 start 함수가 호출되며 마지막 구독자가 구독을 해지하게 된다면 stop 함수가 호출하게 되는 구조입니다.

4. Derived stores

Derived 를 통해서 다른 store에 기초되는 값을 가지는 store를 만들 수 있습니다. 이전 예제를 기반으로 page가 열린 시간을 파생하는 store를 만들 수 있습니다.

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

여러 입력에서 저장소를 파생하고, 값을 반환하는 대신 명시적으로 값을 ‘set’ 할 수 있습니다 ( 비동기적으로 값을 파생하는데 유용)

Derived는 기존에 생성되어있는 쓰기 가능한 store, 읽기 가능한 store의 데이터를 기반으로 동작하게 됩니다.

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

export let count = writable(1)
export let double = derived(count,$count => $count * 2 )
//구독의 의미가 아니라 count 함수의 데이터라는 뜻으로 $를 추가한다.
//svelte 파일이 아닌 js 파일 내 이므로 자동구독으로 실행되지 않음
export let total = derived([count,double], ([$count, $double], set) => {
	set($count+$double)
})
// export let total = derived([count,double], ([$count, $double])=> { return $count+$double } 과 동일하다
export let initialValue = derived(count, ($countm set) => {
	setTimeout(() => {
	set($count + 1)
},1000)
},'최초 계산중 ...')
import { count, double, total } from './store.js'

console.log(double)
</script>

<button on:click={()=> $count += 1 }> click! </button>
<h1>total: {$total}</h1>
<h2>count: {$count}</h2>
<h2>double: {$double}</h2>
<h2>count + 1 : {$initialValue}</h2>

derived는 계산된 store입니다. 따라서 함수의 첫번째 인자로 계산할 대상의 store를 매개변수로 입력하고 두번째는 실행 될 코드를 입력합니다. 두가지 값을 입력할 때는 배열을 사용해서 입력할 수 있습니다. 연결되어있는 스토어의 값이 바뀌면 매번 실행되며 초기화 되었다가 재 구독 하는 형식으로 다른 store들과 차이점이 있습니다.

5. Store 값 얻기 (get)

store에 구독 하지 않고 단순히 해당하는 값만 읽어오고 싶은 경우 사용합니다.

import { get } from 'svelte/store'
import { count, double, user } from './store.js'

console.log(get(count))
console.log(get(double))
console.log(get(user))
import { writable, readable, derived, get } from 'svelte/store'

export let count = writable(1)
export let double = derived(count, $count => $count * 2)
export let user = readable({
	name: 'Heropy'
	age: 85,
	email:'thesecon@gmail.com'
})

console.log(get(count))
console.log(get(double))
console.log(get(user))

6. Custom stores

custom store는 기존의 store에서 추가로 원하는 메소드를 추가하여 커스텀해서 사용 가능한 store입니다. writable, readable 등

<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>
import { writable } from 'svelte/store';

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

	return {
		subscribe, //subscribe를 가지고있으면 App.svelte에서 '$'자동구독을 이용해서 사용 가능
		increment: () => update(n => n + 1),
		decrement: () => update(n => n - 1),
		reset: () => set(0)
	};
}

export const count = createCount();

예제와 같이 기본 writable store에서 increment, decrement, reset 메소드가 추가로 포함되게 구성할 수 있습니다. return 값에서 set 과 update는 입력하지 않아도 됩니다.

import { writable } from 'svelte/store'

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

export let count = {
set,
update,
subscribe,
increment: () => update(n => n + 1),
decrement: () => update(n => n - 1),
reset: () => set(0)
}

위와 같이 작성할 수도 있습니다.

예제 2)

import { fruits } from './fruits.js'

let value

<input bind:value/>
<button on:click={() => fruits.setItem(value)}>
	Add fruit!
</button>
<button on:click={() => console.log(fruits.getList())}>
	Log fruit list!
</button>

<ul>
	{#each $fruits as {id, name} (id)}
		<li>{name}</li>
	{/each}
</ul>
import { writable , get } from 'svelte/store'

const _fruits = writable([
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 3, name: 'Cherry' }
])

export let fruits = {
..._fruits, // writable이기 때문에 set, update, subscribe가 포함되어있다
getList: () => get(_fruits).map((f) => f.name),
setItem: () => { 
	_fruits.update((f) => {
		f.push({
			id: f.length + 1,
			name
		})
		return f
	})
	}
}

7. Store bindings

set 메소드를 가지고 있는 writable store라면 컴포넌트에 bind하는 것과 동일하게 값을 bind 할 수 있습니다.

예를 들어 쓰기 가능한 저장소에 name을 가지고 있고 파생된 저장소에는 greeting을 가지고 있을 때 input 요소에 해당 값을 업데이트 할 수 있습니다.

<input bind:value={$name}>

컴포넌트 내부에 값을 저장하도록 직접 할당할 수도 있습니다.

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

$name += '!'name.set($name + '!') 는 동일합니다.

  • 전체 예제
    <script>
    	import { name, greeting } from './stores.js';
    </script>
    
    <h1>{$greeting}</h1>
    <input bind:value={$name}>
    
    <button on:click="{() => $name += '!'}">
    	Add exclamation mark!
    </button>
    import { writable, derived } from 'svelte/store';
    
    export const name = writable('world');
    
    export const greeting = derived(
    	name,
    	$name => `Hello ${$name}!`
    );
profile
FE DEVELOPER 👩🏻‍💻🤍

0개의 댓글