[svelte store][01] svelte의 reactivity 알아보기

이 문서에서는 svelte store 에 대한 기본적인 사용법을 설명합니다.

1. Assignments

svlete에서 컴포넌트 내에 선언된 변수들은 reactivity를 자동으로 제공합니다.

App.svelte
<script> let weather = { temp: { value: 26, unit: 'C' } } const changeTemperature = (value, unit) => { weather.temp.value = value weather.temp.unit = unit || 'C' } </script>

아래와 같이 화면을 구성할 때 버튼을 누르면 함수 changeTemperature가 실행되면서 "26C"에서 "45F"로 업데이트 됩니다.

App.svelte
<button on:click={() => changeTemperature(45, 'F')}>change</button> <h3>{weather.temp.value}{weather.temp.unit}</h3> <!-- "26C" => "45F" -->

이러한 반응형의 핵심은 "assignment를 사용하는 것" 입니다.

아래의 코드는 모두 동일하게 작동합니다.

TS
// OK weather.temp = {value: 45, unit: 'F'} // OK weather = { temp: {value: 45, unit: 'F'} }
  • assingment(=) 가장 왼쪽의 변수 weather의 상태 변경으로 인식됩니다.

하지만 다음과 같이 값을 넣으면 화면이 업데이트되지 않습니다.

1.1. 하위 property를 통한 업데이트, not reactive

화면에서 weather를 바라볼 때 weather 가 참조하는 인스턴스의 property를 통한 업데이트는 reactive하게 작동하지 않습니다.

App.svelte#01
const changeTemperature = (value, unit) => { // not reactive const { temp } = weather temp.value = 45 temp.unit = 'F' console.log(weather.temp) // prints {value 45, unit: 'F'} }
  • 가장 왼쪽의 변수 temp에 대한 변경으로 인식됨
  • 변수를 통해서 상태 변경은 확인할 수 있으나
  • 화면은 자동으로 바뀌지 않습니다.

변수 temp를 통해서 새로운 날씨 데이터를 쓰기 때문에 화면에서 바라보는 weather에 대한 변경으로 인식하지 못합니다.

마찬가지로 App.svelte#02도 데이터는 변경됐으나 화면은 업데이트되지 않습니다.

App.svelte#02
const { temp } = weather // not reactive const changeTemperature = (value, unit) => { temp.value = 45 temp.unit = 'F' console.log(weather.temp) // prints {value 45, unit: 'F'} }
  • 변수 temp에 대한 변경으로 인식됨

다음과 같이 temp를 바라보도록 뷰(view)를 수정하면 App.svelte#02는 올바로 작동합니다.

App.svelte#02
<h3>{temp.value}{temp.unit}</h3> // updated reactively

이렇게 assignment 를 reactivity의 규칙으로 사용하기 때문에 아무튼 다음과 같이 사용하면 항상 reactive하게 화면을 업데이트할 수 있습니다.

TS
const changeTemperature = (value, unit) => { // do some updates.. weather = weather; // re-assignment }
  • 그다지 우아하지 않지만 아무튼 작동합니다.
  • 위의 코드는 일종의 꼼수일 뿐 권장하는 방법은 아닙니다.

1.2. 메소드를 통한 업데이트, not reactive

마찬가지로 svelte 파일 안에서 method를 통해서 상태를 변경할 때에도 reactive하게 화면이 업데이트되지 않습니다.

App.svelte
<script> import { writable } from 'svelte/store'; let weather = { temp: { value: 26, unit: 'C' }, changeTemp(value, unit) { this.temp.value = value; this.temp.unit = unit || 'C'; } } const changeTemperature = (value, unit) => { weather.changeTemp(value, unit); } </script>
  • weather의 상태는 변경됨. 하지만 화면은 업데이트 되지 않음.

2. Sharing through props

이러한 규칙은 부모 컴포넌트의 상태를 자식 컴포넌트에게 공유할 때도 동일합니다.

중요한 차이점은 밑에서 설명합니다.

아래와 같이 자식 컴포넌트 WeatherView에게 상태를 공유합니다.

App.svelte
<script> import WeatherView from './Weather.svelte'; let weather = { temp: { value: 26, unit: 'C' } } </script> <WeatherView {weather}/>
  • {weather} - weather={weather} 를 간략하게 표현한 문법

자식 컴포넌트 WeatherView.svelte는 아래와 같이 부모가 공유한 weather를 참조합니다.

WeatherView.svelte
<script> export let weather; </script> <span>Current: {weather.temp.value}{weather.temp.unit}</span>
  • 부모 컴포넌트가 공유해준 상태를 참조할 때 export let variable;로 선언합니다.

부모 컴포넌트 App.svelte에서 weather를 변경하면 자식 컴포넌트들(자식의 자식 컴포넌트까지) 모두 reactive하게 화면이 업데이트됩니다.

2.1. 단방향 바인딩

하지만 부모 컴포넌트가 공유해준 상태를 자식 컴포넌트가 수정하면 부모 컴포넌트는 이 변화를 화면에 반영하지 않습니다.

자식 컴포넌트에 다음과 같이 기능을 추가해 봅니다.

WeatherView.svelte
<script> export let weather; const increase = () => { // updates state of parent component weather.temp.value++; } </script> <span>Current: {weather.temp.value}{weather.temp.unit}</span> <button on:click={increase}>Change from child</button>

자식 컴포넌트에서 공유 변수 weather를 통해서 온도를 증가시켰습니다.

부모 컴포넌트 App.svelte에서 참조하는 변수 weather에서도 이 상태 변화는 공유됩니다.

하지만 App.svelte의 화면은 자동으로 업데이트 되지 않습니다.

이것을 svelte에서 단방향 바인딩 이라고 합니다.

2.2. 양방향 바인딩

부모 컴포넌트와 자식 컴포넌트 사이의 규칙을 우회할 수 있는 방법이 있습니다.

다음과 같이 부모 컴포넌트 App.svelte에서 양방향 바인딩 키워드를 적용합니다.

App.svelte
<script> ... </script> <WeatherView bind:weather={weather}/>
  • bind:prop={value} - prop은 자식 컴포넌트에서 선언한 변수이름과 맞춰 줌. 그리고 value는 현재 컴포넌트의 변수를 연결합니다.

이 문법으로 날씨 변수 weather는 마치 전역변수처럼 취급됩니다.

부모 컴포넌트와 자식 컴포넌트 어느 쪽에서 변경하든 모두 이 상태 변화를 공유하고 화면도 모두 업데이트 됩니다.

3. svelte store의 필요성

assignment 를 통한 화면 업데이트 규칙은 편리해 보이긴 하지만, 이것만으로는 많은 불편함이 존재합니다.

3.1. 상태의 ownership 훼손

[2.1. 단방향 바인딩] 의 예제처럼 부모가 공유해준 상태를 자식이 마음대로 변경하는 것은 상태의 ownership을 훼손해서 변경 추적을 어렵게합니다.(디버깅이 어려워진다는 뜻)

이러한 단점을 보완하기 위해서 svelte store 에서는 readable 함수를 제공합니다.

SVELTE
import { readable, writable } from 'svelte/store';
  • 보통 writable로 상태를 감싸고 다른 컴포넌트에 읽기 전용으로 상태를 공유할 때 사용합니다.

3.2. 프로그래밍 제약

반드시 assignment를 통해서만 화면이 변경된다면 모든 상태 변경마다 아래와 같은 꼼수를 써야 합니다.

Svelte
const changeTemperature = (value, unit) => { weather = weather; // re-assignment }
  • 배열의 element를 업데이트 할 때
  • sub property를 통해서 상태를 변경할 때
  • 메소드 호출로 상태를 변경할 때

뷰 화면을 최대한 간결하게 유지하기 위해서는 상태를 관리하는 기능들을 별도의 클래스에 격리하고, 뷰는 메소드 호출과 상태 조회를 통해서 reactive하게 업데이트 되어야 합니다.

뷰에는 로직이 없을 수록 좋습니다.

assignment 를 통한 reactivity 는 유용한 기능이지만 이것만으로는 코드를 간결하게 유지하는데 제약이 많습니다.

svelte store에서 제공하는 기능들을 사용해서 모델과 뷰를 간결하게 구현합니다.