質問やフィードバックがありましたら、フォームからお願いします
本文は台湾華語で、ChatGPT で翻訳している記事なので、不確かな部分や間違いがあるかもしれません。ご了承ください
前言
Svelte のコア理念からわかるように、Svelteはコンパイルプロセスから必要な情報をできるだけ取得し、動的なオーバーヘッドを減らすことを目指しています。前回の記事では、Svelteがどのようにコンパイルしてコードを生成するのかを説明しましたが、今日はSvelteが生成したコードがどのように機能するのかを観察してみましょう。
まずは、簡単なSvelteコンポーネントを見てみましょう:
<script>
import { onMount } from 'svelte';
let count = 1;
onMount(() => {
setInterval(() => count++, 1000);
})
</script>
{#if count != 100}
<span>{count}</span>
{/if}
<p>
this is text
</p>
Svelteコンポーネントの構文は一般的なHTMLと同じですが(ifやawaitのようなテンプレート構文が追加されます)、基本的にはHTMLと完全に互換性があります。しかし、生成されるコンポーネントは JavaScript
となり、例えば上記のコンポーネントはコンパイル後に次のようになります:
// Svelteによって生成された、部分的なコードを省略
import { onMount } from "svelte";
function create_if_block(ctx) {
let span;
let t;
return {
c() {
span = element("span");
t = text(/*count*/ ctx[0]);
},
m(target, anchor) {
insert(target, span, anchor);
append(span, t);
},
p(ctx, dirty) {
if (dirty & /*count*/ 1) set_data(t, /*count*/ ctx[0]);
},
d(detaching) {
if (detaching) detach(span);
}
};
}
function create_fragment(ctx) {
let t0;
let p;
let if_block = /*count*/ ctx[0] != 100 && create_if_block(ctx);
return {
c() {
if (if_block) if_block.c();
t0 = space();
p = element("p");
p.textContent = "this is text";
},
m(target, anchor) {
if (if_block) if_block.m(target, anchor);
insert(target, t0, anchor);
insert(target, p, anchor);
},
p(ctx, [dirty]) {
if (/*count*/ ctx[0] != 100) {
if (if_block) {
if_block.p(ctx, dirty);
} else {
if_block = create_if_block(ctx);
if_block.c();
if_block.m(t0.parentNode, t0);
}
} else if (if_block) {
if_block.d(1);
if_block = null;
}
},
i: noop,
o: noop,
d(detaching) {
if (if_block) if_block.d(detaching);
if (detaching) detach(t0);
if (detaching) detach(p);
}
};
}
function instance($$self, $$props, $$invalidate) {
let count = 1;
onMount(() => {
setInterval(() => $$invalidate(0, count++, count), 1000);
});
return [count];
}
class App extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, {});
}
}
SvelteはSSR機能もサポートしているため、上記のコードをSvelte SSR機能を使ってコンパイルすると、HTML文字列を生成する関数が作成されます。
// Svelteによって生成された、部分的なコードを省略
import { onMount } from "svelte";
const App = create_ssr_component(($$result, $$props, $$bindings, slots) => {
let count = 1;
onMount(() => {
setInterval(() => count++, 1000);
});
return `${count != 100 ? `<span>${escape(count)}</span>` : ``}
<p>this is text
</p>`;
});
export default App;
生成コードの観察(dom)
説明を簡潔にするために、ここでは dom
の生成コードにのみ焦点を当て、SSR
の部分は省略します。
生成されたコードは主に3つの部分で構成されています:create_fragment
関数、instance
関数、そして SvelteComponent
クラスです。
create_fragment
最初に create_fragment
を見てみましょう:
function create_fragment(ctx) {
let t0;
let p;
let if_block = /*count*/ ctx[0] != 100 && create_if_block(ctx);
return {
c() {
if (if_block) if_block.c();
t0 = space();
p = element("p");
p.textContent = "this is text";
},
m(target, anchor) {
if (if_block) if_block.m(target, anchor);
insert(target, t0, anchor);
insert(target, p, anchor);
},
p(ctx, [dirty]) {
if (/*count*/ ctx[0] != 100) {
if (if_block) {
if_block.p(ctx, dirty);
} else {
if_block = create_if_block(ctx);
if_block.c();
if_block.m(t0.parentNode, t0);
}
} else if (if_block) {
if_block.d(1);
if_block = null;
}
},
i: noop,
o: noop,
d(detaching) {
if (if_block) if_block.d(detaching);
if (detaching) detach(t0);
if (detaching) detach(p);
}
};
}
create_fragment
はオブジェクトを返し、その中には多くの単一の英文字の関数が属性として含まれています。これらはそれぞれ異なるライフサイクルで実行されるべきことを表しています:
- c:
create
を表し、コンポーネントを初めて作成するときに実行される関数 - m:
mount
を表し、コンポーネントがDOMにマウントされた後に実行される関数 - p:
patch
を表し、コンポーネントが更新された後に実行される関数 - i:
intro
を表し、コンポーネントがトランジションで進入するときに実行される関数 - o:
outro
を表し、コンポーネントがトランジションで退出するときに実行される関数 - d:
destroy
またはdetach
を表し、コンポーネントがアンマウントされるときに実行される関数
詳細なソースコードと生成ロジックは src/compiler/compile/render_dom/Block.ts で確認できます。
各単語の意味がわかれば、次に何をしているのかがより明確になります:
- 条件式(
if count != 100
)の結果をif_block
に代入 create
時p
要素を作成p.textContent
にthis is text
を代入
mount
時if_block
がtrue
の場合、if_block.m()
を呼び出す(これはマウント時に行うべきこと)t0
をアンカーに挿入p
をアンカーに挿入
patch
時- 条件式
count != 100
が true の場合- すでに
if_block
があればif_block.p()
を呼び出す - ない場合は
create_if_block
を呼び出し、if_block.m()
を実行
- すでに
- 条件式
count != 100
が false の場合if_block
内の要素を削除する必要があるのでif_block.m()
を呼び出す
- 条件式
instance
次に instance
関数を見てみましょう。
function instance($$self, $$props, $$invalidate) {
let count = 1;
onMount(() => {
setInterval(() => $$invalidate(0, count++, count), 1000);
});
return [count];
}
<script>
内のコードはすべて instance
関数に詰め込まれています。ここにはいくつか特別な点があります:
- 元のコードは
setInterval(() => count++, 1000)
ですが、生成後はsetInterval(() => $$invalidate(0, count++, count), 1000)
に変わります。 - 戻り値は配列で、
count
の値を返します。
Svelteは静的解析時に変数の関連情報を把握できるため、依存追跡を自動で行います。ここでの $$invalidate
の動作は、Reactにおける setState
のようなものです。ただし、Reactは手動で追加する必要がありますが、Svelteは自動で検出して処理します。
$$invalidate
の実装は次のようになります(部分的なコードを省略):
// 変数の値が異なることがわかった場合、コンポーネントを dirty に設定(更新が必要)
if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) {
if (!$$.skip_bound && $$.bound[i]) $$.bound[i](value);
if (ready) make_dirty(component, i);
}
function make_dirty(component, i) {
if (component.$$.dirty[0] === -1) {
dirty_components.push(component);
schedule_update();
component.$$.dirty.fill(0);
}
component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31));
}
毎回 setInterval
が count++
をトリガーすると、$$invalidate
が呼び出されます。この時、更新前後の値が同じかどうかを比較し、異なる場合は mark_dirty
関数を呼び出し、コンポーネントを dirty_components
に追加し、更新をスケジュールします。Svelteはバッチ更新機構も実装しており、可能な限り1フレーム内で一度に更新を行います。
SvelteComponent
class App extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, {});
}
}
SvelteComponentの実装は非常にシンプルで、init
関数を呼び出します。この中にはSvelteコンポーネントを初期化し、create_fragment
関数を呼び出して instance
を実行し、コンポーネントを実際にDOMにマウントする主要なロジックが含まれています。
まとめ
Svelteが生成するコードは大きく分けて3つの部分から成ります:create_fragment
、instance
、SvelteComponent
。
create_fragment
:Svelteコンポーネント内の各ライフサイクルがどのように処理されるべきかを示す。instance
:<script>
内のコードを実行し、コンテキスト(props、変数など)を返す。SvelteComponent
:init
関数を使ってSvelteコンポーネントを初期化する。
この記事では、Svelteが生成するコードをどのように解析し、背後にあるリアクティブなメカニズムがどのように達成されるのかを簡単に説明しました(実際にはまだ多くのメカニズムが未提起ですが、今後の記事で説明します)。これで皆さんもSvelteが生成するコードを理解できるようになりましたね!
Svelteに関する他の記事をもっと見たい方はこちらを参照してください。
この記事が役に立ったと思ったら、下のリンクからコーヒーを奢ってくれると嬉しいです ☕ 私の普通の一日が輝かしいものになります ✨
☕Buy me a coffee