svelte에서는 angular, vue, react등 수 많은 reactive 프레임워크는 진정한 reactive가 아니라고 한다. 왜냐하면 가상 돔의 존재때문인데, 변경사항을 가상 돔에 반영하고 그 이후에 진짜 돔에 반영하기 때문이다. 가상돔에 반영하고 가상돔에서 변경된 사항을 비교해서 실제 어느 돔에 반영해야 할지 결정한다. 이러한 과정들이 진짜 돔에 바로 반영한다고 볼 수 없으므로 진정한 reactive라 할 수 없는 것이다.

svelte는 가상 돔을 쓰지 않는다. 그럼 어떻게 reactive하게 만드는 걸까? 바로 컴파일러의 기능을 수행하는 것이다. svelte는 코드를 작성하면 컴파일타임에 바닐라스크립트로 바꾸는데 (이러한 svelte의 특징들은 다른곳을 참고) 이때 어느부분이 reative하게 바뀌어야 되는지를 미리 정의해놓는 것이다. '이 값이 바뀌면 여기 돔이고 저 값은 저기 돔이고' 하는 코드들이 미리 정의되어 있어서 가상돔을 써서 따로 어느 부분이 바뀌었는지 비교할 필요가 없는 것이다.

예를 보자

<script>
    let count = 0;

    function handleClick() {
        count += 1;
    }
</script>

<button on:click={handleClick}>
    Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

(출처: https://svelte.dev/tutorial/reactive-declarations)

위 코드는 버튼을 누른 횟수만큼 버튼의 텍스트가 변경되는 코드이다.
이 코드는 아래와 같이 컴파일 된다.

/* App.svelte generated by Svelte v3.12.1 */
import {
    SvelteComponent,
    append,
    detach,
    element,
    init,
    insert,
    listen,
    noop,
    safe_not_equal,
    set_data,
    space,
    text
} from "svelte/internal";

function create_fragment(ctx) {
    var button, t0, t1, t2, t3_value = ctx.count === 1 ? 'time' : 'times' + "", t3, dispose;

    return {
        c() {
            button = element("button");
            t0 = text("Clicked ");
            t1 = text(ctx.count);
            t2 = space();
            t3 = text(t3_value);
            dispose = listen(button, "click", ctx.handleClick);
        },

        m(target, anchor) {
            insert(target, button, anchor);
            append(button, t0);
            append(button, t1);
            append(button, t2);
            append(button, t3);
        },

        p(changed, ctx) {
            if (changed.count) {
                set_data(t1, ctx.count);
            }

            if ((changed.count) && t3_value !== (t3_value = ctx.count === 1 ? 'time' : 'times' + "")) {
                set_data(t3, t3_value);
            }
        },

        i: noop,
        o: noop,

        d(detaching) {
            if (detaching) {
                detach(button);
            }

            dispose();
        }
    };
}

function instance($$self, $$props, $$invalidate) {
    let count = 0;

    function handleClick() {
        $$invalidate('count', count += 1);
    }

    return { count, handleClick };
}

class App extends SvelteComponent {
    constructor(options) {
        super();
        init(this, options, instance, create_fragment, safe_not_equal, []);
    }
}

export default App;

svelte는 c(create) 함수에서 dom을 노드 별로 잘게 쪼개서 정의해 놓는다. 버튼카운터가 올라가야 할 노드는 t1 = text(ctx.count);로 선언한 t1노드이다.

아래 instance란 메소드에서 클릭이벤트가 일어나면 count가 변경됬다고 알리고 값을 변경하고

 function handleClick() {
   $$invalidate('count', count += 1);
 }

p함수에서 count가 변경되어 있을 시 t1의 값을 변경한다.

if (changed.count) {
    set_data(t1, ctx.count);
}

이렇게 미리 정의되어 있기 때문에 특정 값이 바뀌면 반응형으로 바로 해당 target의 노드가 변경되는 것이다.

이러한 특징 덕분에 svelte에서는 reative한 로직들을 쉽게 다룰 수 있는데

let count = 0;
$:double = count * 2;

위와 같이 선언하면 double은 count가 변경될때마다 바로 두배의 값으로 변한다.

function instance($$self, $$props, $$invalidate) {
    let count = 0;
    function handleClick() {
        $$invalidate('count', count += 1);
    }

    let double;

    $$self.$$.update = ($$dirty = { count: 1 }) => {
        if ($$dirty.count) { $$invalidate('double', double = count * 2); }
    };

    return { count, handleClick, double };
}

마찬가지로 instance 메서드에 $$invalidate에서 count의 변경을 호출하면 $$update함수에서 $$dirty의 count변수로 들어오고 이때 double의
값을 변경하면 된다.

이런 구조라면 다른 프레임워크처럼 watch를 쓰지 않아도 변경이 일어날때마다 아래 같은 조건문을 실행하는 건 쉬운일이다.

$: if (double > 4) {
    doSomething();
}

미리 컴파일 하기 때문에 불필요한 css 또한 미리 걸러내 번들에 포함이 되지 않는다. 이는 css도 svelte의 컴퍼넌트 scope단위라서 가능한 일이다.