(Svelte) useEffect 처럼 상태 구독하기

Nov 30, 2023

React 진영 개발자가 Svelte 입문하게 된다면 던지는 단골 질문이 하나 있습니다.
“Svelte에서 어떻게 React의 useEffect 처럼 상태를 구독할 수 있나요?”

이를 답하기 위해선, Svelte 특유의 reactive 문법을 상황에 따라 잘 활용해야 합니다.

간단한 상태 구독

React에서는 useEffect 훅을 사용하여 컴포넌트의 상태 변화를 감지하고, 특정 상태가 변경될 때마다 부수 효과(side effects)를 실행합니다.

useEffect(() => {
  console.log(name);
}, [name]);

Svelte에서는 reactive statement(반응형 선언)을 사용하여 유사한 기능을 구현할 수 있습니다. Svelte의 반응형 선언은 $: 기호를 사용하여 정의되며, Svelte 컴파일러는 표현식 안에 사용된 모든 변수를 파악하여 해당 값이 변경될 때마다 자동으로 다시 표현식 구문을 실행합니다.

<script>
  $: console.log(name);
 
  // 여러 줄 형태
  $: {
    console.log(name);
  }
 
  // 즉시 실행 함수 형태
  $: (() => {
    console.log(name);
  })();
</script>

특정 상태만 구독

React는 의존성 배열을 활용하여 특정 상태에만 의존하는 부수 효과를 실행할 수 있습니다.

useEffect(() => {
  console.log(state1, state2, state3);
  // state1이 변경될 때만 수행된다.
}, [state1]);

하지만 Svelte에서는 $: 기호를 사용한 반응형 선언이 모든 관련 상태의 변경에 반응합니다. 즉, state1, state2, state3 중 어느 하나라도 변경되면 부수 효과가 실행됩니다.

<script>
  $: console.log(state1, state2, state3);
  // 🚨 state1, state2, state3이 변경될 때 모두 수행된다.
</script>

이를 해결하기 위해, 불필요한 의존성을 $: 표현식 밖으로 내보내면 됩니다. 즉, 부수 효과 함수를 별도로 선언하고 의존할 상태만을 건네주면 됩니다.

<script>
  const effect = (state1) => {
    console.log(state1, state2, state3);
  }
  $: effect(state1);
</script>

더 React 스럽게 작성한다면 다음과 같이 할 수도 있습니다.

<script>
  const effect = () => {
    console.log(state1, state2, state3);
  }
  $: effect(), [state1];
 
  // $: (() => {
  // 	effect();
  // })(), [state1];
</script>

조금 난해할 수 있지만 함수에 인자를 건네 줄 필요가 없어 코드가 더 간결한 것 같습니다. 특히 Typescript에서 더 빛을 발합니다.

reactive 과 rendering

Svelte는 React와 다르게 랜더링과 상태 구독이 별개로 진행됩니다. 따라서 랜더링 이후의 DOM을 접근하려면 tick을 활용해야하는 점을 꼭 유의하셔야 합니다.

<script>
  import {tick} from "svelte";
 
  let btnEl;
  let cnt = 1;
 
  const checkBtnWidth = async () => {
    if(!btnEl) return;
 
    // 상태가 DOM에 적용되는 것을 보장하는 promise
    await tick();
 
    const { width } = btnEl.getBoundingClientRect();
    console.log(width);
  }
  $: checkBtnWidth(), [cnt];
 
  const onClick = () => {
    cnt = cnt * 10;
  }
</script>
 
<button bind:this={btnEl} on:click={onClick}>
  {cnt}
</button>

REPL에서 직접 테스트 해보세요!

맺으면서

Svelte의 진입 장벽이 타 프레임워크에 비해 낮다고 합니다. 분명히 쉽게 간결한 코드를 작성할 수 있는 점이 있지만 Svelte도 React만큼이나 프레임워크에 대한 이해도가 뒷바침 되어야 복잡한 웹 어플리케이션을 다룰 수 있는 것 같습니다.

최근 Svelte 5에 대한 예고와 함께 runes이라는 새로운 reactive 시스템이 소개되었는데요. 코드 형태가 조금 기괴(?)해졌지만 개인적으로 더 직관적으로 의존성을 다룰 수 있는 것 같습니다. 프론트엔드 초심자에게 조금 더 진입장벽이 생겼지만, React 진영의 개발자는 더 쉽게 Svelte로 정착할 수 있지 않을까 기대합니다.