이번 포스팅에서는 스벨트에서 사용하는 반응형 문법에 대해 알아보자. 특히 스벨트의 $ 문법을 눈여겨 보자!
사용자 행동(클릭 등) 또는 데이터 변화에 따라 자동으로 데이터 혹은 화면이 업데이트되는 것
<script>
let count = 0;
setInterval(() => count += 1, 1000);
</script>
<p>count: {count}</p>
count 값이 업데이트되면 반응형 동작으로 화면이 업데이트된다. 데이터가 할당되면 반응형 동작을 하게 되는데, 데이터가 할당되는 대표적인 경우는 이벤트가 발생하여 이벤트 핸들러에서 데이터를 업데이트하는 경우다.
{button on=이벤트 이름="{이벤트 핸들러}">Click</button>
이벤트 리스너는 HTML 태그에 이벤트가 발생하는지 감시하고 있다가 이벤트가 발생했을 때 이벤트 핸들러를 호출한다. 이벤트 이름에는 click, change, mousedown 등이 온다.
<script>
let count = 0;
function handleClick(event) {
count += 1;
}
</script>
<button on:click={handleClick}>{count}</button>
<script>
let count = 0;
</script>
<button on:click={(event) => count += 1}>{count}</button>
일부 프레임워크에서는 성능상의 이유로 인라인 이벤트 핸들러 사용을 지양하고 있다. 그러나 스벨트는 컴파일되면서 최적화되기 때문에 인라인 핸들러를 성능 고민 없이 사용할 수 있다.
스벨트의 반응형은 데이터가 할당되었을 때 동작한다. Number, String 타입 등의 기본형 변수가 아닌 Array, Object 타입의 참조형 변수는 데이터 할당 없이 참조된 데이터 업데이트가 가능하다.
const arr = [];
arr.push('Hello World!');
arr[0] = 'helloWorld';
const obj = { foo: 'bar' };
obj.foor = 'baz';
참조형 변수의 값을 데이터 할당 없이 업데이트할 수 있더라도, 참조형 변수 역시 데이터 할당이 되어야 반응형 동작을 하게 된다.
배열의 push, splice 등의 메서드를 사용하여 배열의 참조 값을 업데이트한 경우에는 자동으로 화면을 업데이트하지 않는다. 아래와 같이 할당이 되어야 반응형으로 동작한다.
<script>
let numbers = [];
function addNumber(){
// numbers.push(numbers.length + 1);
// numbers = numbers;
numbers = [...numbers, numbers.length + 1];
};
</script>
<p>{numbers}</p>
<button on:click={addNumber}>추가</button>
객체 데이터를 할당할 때 할당되는 변수가 할당 식의 가장 왼쪽에 나타나야 한다. 다음 코드와 같이 할당할 경우 반응형 동작을 하지 않는다.
<script>
let obj = {
foo: {
bar: 'bar'
}
};
function handleClick() {
const foo = obj.foo;
foo.bar = 'baz'; // 반응형으로 동작하지 않음.
}
</script>
<p>{obj.foo.bar}</p>
<button on:click={handleClick}>변경</button>
다음과 같이 수정해야 반응형으로 동작한다.
<script>
let obj = {
foo: {
bar: 'bar'
}
};
function handleClick() {
obj.foo.bar = 'baz';
};
</script>
<p>{obj.foo.bar}</p>
<button on:click={handleClick}>변경</button>
$
문법은 특정 데이터를 감시한다. $ 문법을 사용하면 특정 데이터가 업데이트되었을 때 필요한 동작을 수행하는 코드를 작성할 수 있다. $ 문법을 사용하는 방식에 따라 반응형 선언, 반응형 실행으로 구분할 수 있다.
데이터가 업데이트되었을 때 자동으로 값을 선언하는 방법
<script>
let count = 0;
$: doubled = count + 2; // count 업데이트 시 재계산 된다.
</script>
<button on:click={(event) => count += 1}>count: {count}</button>
<p>doubled: {doubled}</p>
count 값이 업데이트되면 $: 블록이 반응형으로 실행되어 doubled 값을 업데이트한다. 이처럼 let doubled와 같은 변수 선언이 필요하지 않다.
<script>
let count = 0;
$: console.log(`count: ${count}`);
</script>
<button on:click={(event) => count += 1}>count: {count}</button>
count 값이 업데이트될 때마다 값을 console.log로 출력한다.
<script>
let count = 0;
$: {
console.log(`count: ${count}`);
if(count >= 10){
alert('count가 10보다 큽니다.');
}
}
</script>
<button on:click={(event) => count += 1}>count: {count}</button>
이렇게 블록으로 작성도 가능하다.
<script>
let count = 0;
let isEnter = false;
$: {
// count 또는 isEnter 값이 변경될 때 실행된다.
console.log(`count: ${count}, isEnter: ${isEnter}`);
}
</script>
<p>isEnter: {isEnter}</p>
<button
on:click={(e) => count += 1}
on:mouseenter={() => isEnter = true}
on:mouseleave={() => isEnter = false}
>
count: {count}
</button>
$: 블록에서 두 개 이상의 변수를 사용한다면 두 개의 변수를 모두 감시한다. count 또는 isEnter 값이 변경될 경우 $: 블록이 실행된다.
$: 블록은 첫 렌더링 시에도 실행된다.
$ 문법은 최상위 레벨에서 선언되어야 한다. 함수 안이나 블록문 안에서 사용되면 데이터를 감시할 수 없다.
<script>
let count = 0;
{
// 블록 안에서 사용한 $는 데이터를 감시하지 못함
$: console.log(`count: ${count}`)
}
function init() {
// 함수 안에서 사용한 $는 데이터를 감시하지 못함
$: console.log(`count: ${count}`)
}
init();
</script>
<button on:click={(e) => count += 1}>count: {count}</button>
$ 문법은 $: 블록에서 사용된 데이터를 감시한다. 사용되네이터를 모두 감시하기 때문에 사용된 데이터 중 하나라도 업데이트되면 $: 블록이 실행된다. 하나의 $: 블록이 많은 양의 데이터를 감시하게 되면, 데이터가 업데이트되었을 때마다 불필요한 코드가 실행될 수 있어 성능 저하가 발생하게 된다. 감시하는 데이터를 분리할 수 있다면 여러 개의 $: 블록으로 분리하여 필요한 코드만 동작하게 하는 것이 좋다.
$: 블록에서 직접 사용되는 값들만 감시하여 반응형으로 동작한다.
<script>
let x = 0;
let y = 0;
function yPlusAValue(value) {
return value + y;
}
$: total = yPlusAValue(x);
</script>
total: {total}
<button on:click={() => x++}>
Increment X
</button>
<button on:click={() => y++}>
Increment Y
</button>
$: 블록에서 yPlusAValue 함수의 파라미터로 x 값을 전달해 주면 x와 y의 값이 합쳐져 total 값이 업데이트된다. x나 y 값이 변경되면 $: 블록이 실행되어 total 값을 업데이트해야 할 것 같지만, 실제로는 $: 블록에 직접 사용된 값은 x뿐이기 때문에 x 값이 업데이트될 때만 반응형으로 동작한다.