29
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ラクスAdvent Calendar 2021

Day 19

Svelteが仮想DOMを使用していないってどういうこと

Last updated at Posted at 2021-12-19

はじめに

数年前からフロントエンド界隈で話題になっているSvelteですが、最近重い腰をあげて学び始めました。
その中で「仮想DOMを使用しない」というのが具体的にどういうことのか実動作ベースで調べてみました。

Svelteとは

概要

  • JavaScriptフレームワークの1種
  • Vue.jsReactのようにWebアプリケーションやUIを構築する

3つの特徴

  1. Write less code(少ないコード量で記述)
  2. No Virtual DOM(仮想DOMを使用しない) ← 今回の記事の目的
  3. Truly reactive(真にリアクティブ)

調査用のSvelteプロジェクトを作成

テンプレートをダウンロード

$ npx degit sveltejs/template my-svelte-project
$ cd my-svelte-project
$ npm install
$ npm run dev

DOMの変化がわかりやすいようにApp.svelteを改変

(余談) QiitaのコードブロックはSvelteに対応していないっぽい。。。以下はvueで代用

<script>
	let greetingText = "Hello";
	let areaName = "World";
	function changeData() {
		greetingText = greetingText == "Hello" ? "Good Night" : "Hello";
		areaName = areaName == "World" ? "Japan" : "World";
	}
</script>

<main>
	<h1>{greetingText} Svelte {areaName}!</h1>
	<a href="https://svelte.dev/tutorial">Svelte tutorial</a>
	<button on:click={changeData}>change</button>
</main>

<style>
	main {
		text-align: center;
		padding: 1em;
		max-width: 240px;
		margin: 0 auto;
	}

	h1 {
		color: #ff3e00;
		text-transform: uppercase;
		font-size: 4em;
		font-weight: 100;
	}

	@media (min-width: 640px) {
		main {
			max-width: none;
		}
	}
</style>

アプリを表示

  • http://localhost:5000/ にアクセス
  • changeボタン押下でメッセージが切り替わる簡単なアプリ

demo.gif

「仮想DOMを使用しない」の検証

devツールを開いて生成物を確認

image.png

bundle.jsを覗いてみる


(function(l, r) { if (!l || l.getElementById('livereloadscript')) return; r = l.createElement('script'); r.async = 1; r.src = '//' + (self.location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1'; r.id = 'livereloadscript'; l.getElementsByTagName('head')[0].appendChild(r) })(self.document);
var app = (function () {
    'use strict';

    …(中略)…

    function create_fragment(ctx) {
    	let main;
    	let h1;
    	let t0;
    	let t1;
    	let t2;
    	let t3;
    	let t4;
    	let a;
    	let t6;
    	let button;
    	let mounted;
    	let dispose;

    	const block = {
    		c: function create() {
    			main = element("main");
    			h1 = element("h1");
    			t0 = text(/*greetingText*/ ctx[0]);
    			t1 = text(" Svelte ");
    			t2 = text(/*areaName*/ ctx[1]);
    			t3 = text("!");
    			t4 = space();
    			a = element("a");
    			a.textContent = "Svelte tutorial";
    			t6 = space();
    			button = element("button");
    			button.textContent = "change";
    			attr_dev(h1, "class", "svelte-1tky8bj");
    			add_location(h1, file, 10, 1, 231);
    			attr_dev(a, "href", "https://svelte.dev/tutorial");
    			add_location(a, file, 11, 1, 275);
    			add_location(button, file, 12, 1, 334);
    			attr_dev(main, "class", "svelte-1tky8bj");
    			add_location(main, file, 9, 0, 223);
    		},
            ...中略...
    		m: function mount(target, anchor) {
    			insert_dev(target, main, anchor);
    			append_dev(main, h1);
    			append_dev(h1, t0);
    			append_dev(h1, t1);
    			append_dev(h1, t2);
    			append_dev(h1, t3);
    			append_dev(main, t4);
    			append_dev(main, a);
    			append_dev(main, t6);
    			append_dev(main, button);

    			if (!mounted) {
    				dispose = listen_dev(button, "click", /*changeData*/ ctx[2], false, false, false);
    				mounted = true;
    			}
    		},
    		p: function update(ctx, [dirty]) {
    			if (dirty & /*greetingText*/ 1) set_data_dev(t0, /*greetingText*/ ctx[0]);
    			if (dirty & /*areaName*/ 2) set_data_dev(t2, /*areaName*/ ctx[1]);
    		},
            ...中略...
    	};
        ...中略...
    }

    function instance($$self, $$props, $$invalidate) {
    	let { $$slots: slots = {}, $$scope } = $$props;
    	validate_slots('App', slots, []);
    	let greetingText = "Hello";
    	let areaName = "World";

    	function changeData() {
    		$$invalidate(0, greetingText = greetingText == "Hello" ? "Good Night" : "Hello");
    		$$invalidate(1, areaName = areaName == "World" ? "Japan" : "World");
    	}

    	const writable_props = [];

    	Object.keys($$props).forEach(key => {
    		if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$' && key !== 'slot') console.warn(`<App> was created with unknown prop '${key}'`);
    	});

    	$$self.$capture_state = () => ({ greetingText, areaName, changeData });

    	$$self.$inject_state = $$props => {
    		if ('greetingText' in $$props) $$invalidate(0, greetingText = $$props.greetingText);
    		if ('areaName' in $$props) $$invalidate(1, areaName = $$props.areaName);
    	};

    	if ($$props && "$$inject" in $$props) {
    		$$self.$inject_state($$props.$$inject);
    	}

    	return [greetingText, areaName, changeData];
    }
    …(中略...
})();
//# sourceMappingURL=bundle.js.map

もともとApp.svelteに記載していた内容がすべてバニラJSに変換されており、changeData関数で変更する変数greetingTextareaNameが変更された際のDOMへの追従処理も全て記述されている。

Svelteはコンパイラである

Svelteはユーザーインタフェースを構築する先鋭的で新しいアプローチです。ReactやVueのような従来のフレームワークがその作業の大部分を ブラウザ で行うのに対し、Svelteはその作業をアプリをビルドする際の コンパイル時 に行います。

コンパイル時に変更可能性を検知しておくってこと?

Vue.jsやReactを使用したアプリだと

  1. ブラウザ側でフレームワークのコードを解釈
  2. データの変更を元に仮想DOMを変更
  3. 仮想DOMと実際のDOMの差分を検知
  4. 差分部分のみを実際のDOMに反映

とするところを、Svelteは

  1. コンパイル時に変更される可能性がある変数などをすべてバニラJSに落とし込む
  2. データの変更があった場合は生成されているJSの該当コードが走るだけ(実際のDOMがそのまま変更される)

となるので、ブラウザ(クライアント)側でのオーバーヘッドが下がるといったところでしょうか?

おわり

実際にコードを見てみることでSvelteの挙動が少しは分かったと思います。が、まだまだ触り始めたばかりなので間違った解釈をしている箇所がありそうです。もし詳しい方がいらっしゃいましたらご指摘いただけると幸いです。

実際にアプリを開発する際はフレームワークの挙動をすべて把握していなくとも問題なく開発を進めることができるケースが多いですが、この界隈はどんどん新しい技術が出てくるため世の中に置いていかれないようにキャッチアップを続けていきます。

ひとまずはSvelteのチュートリアルをやりきります

29
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?