CheatSheet
日本語 icon日本語English iconEnglish
チートシートとはカンニングペーパーのことです。それが転じて、本来覚えることをまとめておいたものです。
要点をすぐに参照できるようにまとめてみました。

Svelte

エンジニアのためのWebチートシート

Svelteはコンパイラベースのフロントエンドフレームワークです。 Svelte 5のRunes($state, $derived, $effect等)による新しいリアクティビティシステムを中心に、テンプレート構文、バインディング、トランジション、SvelteKitの基本をチートシートにまとめました。

コンポーネント基本

コンポーネント構造

  • script(ロジック)、マークアップ(HTML)、style(CSS)の3セクションで構成されます。

    <script>
      let count = $state(0);
    
      function increment() {
        count++;
      }
    </script>
    
    <button onclick={increment}>
      Count: {count}
    </button>
    
    <style>
      button {
        font-size: 1.2rem;
        padding: 0.5rem 1rem;
      }
    </style>

TypeScript対応

  • lang="ts" でTypeScriptを使用できます。

    <script lang="ts">
      let name: string = $state('World');
      let count: number = $state(0);
    
      interface User {
        id: number;
        name: string;
      }
      let user: User = $state({ id: 1, name: 'Alice' });
    </script>
    
    <h1>Hello {name}!</h1>

スコープドCSS

  • スタイルはコンポーネントにスコープされます。:global() でグローバルスタイルを適用できます。

    <style>
      /* Scoped to this component */
      p { color: blue; }
    
      /* Global style */
      :global(body) {
        margin: 0;
      }
    
      /* Nested global */
      div :global(strong) {
        color: red;
      }
    </style>

Runes(ルーン)

$state - リアクティブ状態

  • リアクティブな状態を宣言します。オブジェクト・配列はディープリアクティブです。

    // Basic
    let count = $state(0);
    
    // Object (deep reactivity)
    let user = $state({ name: 'Alice', age: 30 });
    user.name = 'Bob'; // auto-updates UI
    
    // Array (deep reactivity)
    let items = $state(['Apple', 'Banana']);
    items.push('Cherry'); // auto-updates UI
    
    // $state.raw (no deep reactivity)
    let data = $state.raw({ name: 'Alice' });
    data.name = 'Bob';            // NOT reactive
    data = { name: 'Bob' };       // reactive
    
    // $state.snapshot (get plain object)
    let obj = $state({ count: 0 });
    console.log($state.snapshot(obj));
    
    // In classes
    class Todo {
      done = $state(false);
      text = $state('');
      constructor(text) { this.text = text; }
      toggle() { this.done = !this.done; }
    }

$derived - 派生状態

  • 他の状態から自動計算される値です。メモ化され、依存値が変わった時のみ再計算されます。

    let count = $state(0);
    let doubled = $derived(count * 2);
    let isEven = $derived(count % 2 === 0);
    
    // $derived.by (complex computation)
    let total = $derived.by(() => {
      let sum = 0;
      for (const n of numbers) {
        sum += n;
      }
      return sum;
    });

$effect - 副作用

  • マウント時と依存値の変更時に実行されます。クリーンアップ関数を返せます。

    // Runs on mount + dependency change
    $effect(() => {
      console.log(`Count: ${count}`);
    
      // Cleanup (before re-run / on destroy)
      return () => {
        console.log('cleanup');
      };
    });
    
    // $effect.pre (before DOM update)
    $effect.pre(() => {
      // e.g., save scroll position
    });

$props - プロパティ

  • 親コンポーネントから渡されるプロパティを受け取ります。

    <!-- Child component -->
    <script lang="ts">
      interface Props {
        name: string;
        count?: number;
        class?: string;
      }
    
      let {
        name,
        count = 0,
        class: klass,
        ...rest
      } = $props<Props>();
    </script>
    
    <div class={klass} {...rest}>
      <p>{name}: {count}</p>
    </div>
    
    <!-- Parent component -->
    <MyComponent name="Alice" count={5} />

$bindable & $inspect

  • $bindableは双方向バインディング可能なプロパティ、$inspectはデバッグ専用のルーンです。

    <!-- $bindable: two-way binding prop -->
    <script>
      let { value = $bindable('') } = $props();
    </script>
    <input bind:value={value} />
    
    <!-- Parent: bind:value -->
    <FancyInput bind:value={message} />
    
    <!-- $inspect: debug only (dev mode) -->
    <script>
      $inspect(count, message);
      $inspect(count).with((type, val) => {
        if (type === 'update') debugger;
      });
    </script>

テンプレート構文

{#if} & {#each}

  • 条件分岐とリストレンダリングです。

    <!-- Conditional rendering -->
    {#if temperature > 100}
      <p>too hot!</p>
    {:else if temperature < 80}
      <p>too cold!</p>
    {:else}
      <p>just right!</p>
    {/if}
    
    <!-- List rendering -->
    {#each items as item}
      <li>{item.name}</li>
    {/each}
    
    <!-- With index -->
    {#each items as item, i}
      <li>{i + 1}: {item.name}</li>
    {/each}
    
    <!-- Keyed (efficient updates) -->
    {#each items as item (item.id)}
      <li>{item.name}</li>
    {/each}
    
    <!-- Destructured + keyed -->
    {#each items as { id, name, qty }, i (id)}
      <li>{name} x {qty}</li>
    {/each}
    
    <!-- Empty fallback -->
    {#each todos as todo}
      <p>{todo.text}</p>
    {:else}
      <p>No tasks today!</p>
    {/each}

{#await} & {#key}

  • Promise処理と値変更時の再生成です。

    <!-- Promise handling -->
    {#await promise}
      <p>Loading...</p>
    {:then value}
      <p>Result: {value}</p>
    {:catch error}
      <p>Error: {error.message}</p>
    {/await}
    
    <!-- Skip pending state -->
    {#await promise then value}
      <p>Result: {value}</p>
    {/await}
    
    <!-- Re-create on value change -->
    {#key value}
      <Component />
    {/key}
    
    {#key value}
      <div transition:fade>{value}</div>
    {/key}
    
    <!-- Local constant -->
    {#each items as item}
      {@const total = item.price * item.qty}
      <p>{item.name}: {total}</p>
    {/each}
    
    <!-- Raw HTML (XSS caution) -->
    {@html '<strong>Bold</strong>'}

{#snippet} & {@render}

  • 再利用可能なテンプレート片の定義とレンダリングです(Svelte 5でslotを置換)。

    <!-- Define snippet -->
    {#snippet greeting(name)}
      <p>Hello, {name}!</p>
    {/snippet}
    
    <!-- Render snippet -->
    {@render greeting('World')}
    
    <!-- Pass snippets to components -->
    <Table data={fruits}>
      {#snippet header()}
        <th>Name</th><th>Qty</th>
      {/snippet}
      {#snippet row(item)}
        <td>{item.name}</td><td>{item.qty}</td>
      {/snippet}
    </Table>
    
    <!-- children snippet (default content) -->
    <!-- Card.svelte -->
    <script>
      let { children } = $props();
    </script>
    <div class="card">
      {@render children?.()}
    </div>
    
    <!-- Usage -->
    <Card>
      <p>Card content here</p>
    </Card>
    
    <!-- Optional with fallback -->
    {#if children}
      {@render children()}
    {:else}
      <p>Fallback content</p>
    {/if}

イベント & バインディング

イベントハンドリング

  • Svelte 5ではHTML標準のイベント属性(onclick等)を使用します。

    <!-- Basic handler -->
    <button onclick={handleClick}>Click</button>
    
    <!-- Inline handler -->
    <button onclick={() => count++}>+1</button>
    
    <!-- Shorthand (same name) -->
    <script>
      function onclick() { count++; }
    </script>
    <button {onclick}>Click</button>
    
    <!-- Event modifiers (Svelte 5 style) -->
    <script>
      function handleSubmit(event) {
        event.preventDefault();
        // ...
      }
      function handleOnce(event) {
        // runs once, then removes handler
      }
    </script>
    <form onsubmit={handleSubmit}>...</form>
    
    <!-- Capture phase -->
    <button onclickcapture={handler}>
      Capture
    </button>

バインディング

  • bind:ディレクティブによるフォーム要素やDOM参照への双方向バインディングです。

    <!-- Text input -->
    <input bind:value={name} />
    
    <!-- Number (auto-converts) -->
    <input type="number" bind:value={count} />
    <input type="range" bind:value={vol} />
    
    <!-- Checkbox -->
    <input type="checkbox" bind:checked={ok} />
    
    <!-- Radio group -->
    {#each ['A', 'B', 'C'] as opt}
      <label>
        <input type="radio" bind:group={sel}
          value={opt} /> {opt}
      </label>
    {/each}
    
    <!-- Select -->
    <select bind:value={selected}>
      {#each options as opt}
        <option value={opt.id}>{opt.name}</option>
      {/each}
    </select>
    
    <!-- textarea -->
    <textarea bind:value={content}></textarea>
    
    <!-- DOM reference -->
    <canvas bind:this={canvasEl}></canvas>
    
    <!-- Element dimensions (read-only) -->
    <div bind:clientWidth={w}
         bind:clientHeight={h}>
      {w} x {h}
    </div>

コンポーネントイベント

  • Svelte 5ではコールバックプロパティでイベントを伝播します。

    <!-- Child: callback prop -->
    <script>
      let { onsubmit } = $props();
    </script>
    <button onclick={() => onsubmit({
      text: 'Hello'
    })}>
      Submit
    </button>
    
    <!-- Parent: pass callback -->
    <ChildComponent onsubmit={(data) => {
      console.log(data.text);
    }} />
    
    <!-- Spread events from parent -->
    <script>
      let { onclick, onhover, ...rest } = $props();
    </script>
    <button {onclick} {...rest}>
      Click me
    </button>

トランジション & アニメーション

トランジションの使い方

  • 要素の出入りにアニメーションを適用します。

    <script>
      import { fade, fly, slide, scale }
        from 'svelte/transition';
      import { flip } from 'svelte/animate';
      let visible = $state(true);
    </script>
    
    <!-- Both directions -->
    {#if visible}
      <div transition:fade>Fades</div>
      <div transition:fly={{ y: 200, duration: 500 }}>
        Flies
      </div>
      <div transition:slide={{ axis: 'x' }}>
        Slides
      </div>
    {/if}
    
    <!-- Different in/out transitions -->
    {#if visible}
      <div in:fly={{ y: -200 }} out:fade>
        Different in/out
      </div>
    {/if}
    
    <!-- Transition events -->
    <div
      transition:fly={{ y: 200 }}
      onintrostart={() => status = 'intro'}
      onoutroend={() => status = 'done'}
    >
      Content
    </div>

組み込みトランジション一覧

トランジション効果主要パラメータ
fade不透明度delay, duration
blurぼかし + 不透明度amount, duration
fly移動 + 不透明度x, y, duration
slideスライドaxis, duration
scale拡大縮小start, duration
drawSVG描画speed, duration
crossfade要素間の遷移fallback, duration

アニメーション(keyed each内)

  • {#each}でキーを使う場合、要素の並べ替えをアニメーションできます。

    <script>
      import { flip } from 'svelte/animate';
    </script>
    
    {#each items as item (item.id)}
      <div animate:flip={{ duration: 300 }}>
        {item.name}
      </div>
    {/each}

ストア & 状態共有

共有状態(Svelte 5推奨)

  • .svelte.js ファイルでrunesを使い、コンポーネント間で状態を共有します。

    // shared-state.svelte.js
    export const counter = $state({ count: 0 });
    
    export function increment() {
      counter.count++;
    }
    
    export function reset() {
      counter.count = 0;
    }
    <!-- Any component -->
    <script>
      import { counter, increment }
        from './shared-state.svelte.js';
    </script>
    
    <button onclick={increment}>
      Count: {counter.count}
    </button>

従来のストア

  • writable/readable/derivedストアは引き続き利用可能です。

    import { writable, readable, derived }
      from 'svelte/store';
    
    // writable: read-write
    const count = writable(0);
    count.set(1);
    count.update(n => n + 1);
    
    // readable: read-only
    const time = readable(new Date(), (set) => {
      const interval = setInterval(
        () => set(new Date()), 1000
      );
      return () => clearInterval(interval);
    });
    
    // derived: computed
    const doubled = derived(
      count, $count => $count * 2
    );
    <!-- Auto subscribe with $ prefix -->
    <script>
      import { count } from './stores.js';
    </script>
    <p>Count: {$count}</p>
    <button onclick={() => $count++}>+1</button>

特別な要素 & ディレクティブ

特別な要素

  • svelte:window、svelte:head 等のシステム要素です。

    <!-- Window events & bindings -->
    <svelte:window
      onkeydown={handleKeydown}
      bind:scrollY={y}
      bind:innerWidth={w}
    />
    
    <!-- Document events -->
    <svelte:document
      onvisibilitychange={handleVisibility}
    />
    
    <!-- Body events -->
    <svelte:body onmouseenter={handleEnter} />
    
    <!-- Head (meta tags, title) -->
    <svelte:head>
      <title>My Page</title>
      <meta name="description" content="..." />
    </svelte:head>
    
    <!-- Dynamic HTML element -->
    <svelte:element this={tag}>
      Content
    </svelte:element>
    
    <!-- Dynamic component -->
    <svelte:component this={component} />

class & style ディレクティブ

  • 条件付きクラスとインラインスタイルの設定です。

    <!-- class directive -->
    <div class:active={isActive}>
      Conditional class
    </div>
    <div class:active>
      Shorthand (var name = class name)
    </div>
    
    <!-- Object syntax (v5.16+) -->
    <div class={{
      active: isActive,
      bold: isBold
    }}>
      Multiple classes
    </div>
    
    <!-- style directive -->
    <div style:color={textColor}>Styled</div>
    <div style:--custom="value">CSS var</div>
    <div style:transform={
      flipped ? 'rotateY(0)' : 'rotateY(180deg)'
    }>
      Flip
    </div>
    
    <!-- Conditional attribute -->
    <button disabled={!isValid}>Submit</button>

SvelteKit ルーティング

ファイルベースルーティング

  • src/routes/ ディレクトリ構造でルーティングが決まります。

    src/routes/
    ├── +page.svelte           → /
    ├── +layout.svelte         → shared layout
    ├── +error.svelte          → error page
    ├── about/
    │   └── +page.svelte       → /about
    ├── blog/
    │   ├── +page.svelte       → /blog
    │   ├── +page.server.ts    → server load
    │   └── [slug]/
    │       ├── +page.svelte   → /blog/:slug
    │       └── +page.ts       → universal load
    ├── api/
    │   └── posts/
    │       └── +server.ts     → /api/posts
    └── (auth)/                → route group
        ├── +layout.svelte
        ├── login/
        │   └── +page.svelte   → /login
        └── register/
            └── +page.svelte   → /register

レイアウト

  • 共通レイアウトの定義です。children スニペットで子ページを挿入します。

    <!-- +layout.svelte -->
    <script>
      let { children } = $props();
    </script>
    
    <nav>
      <a href="/">Home</a>
      <a href="/about">About</a>
    </nav>
    
    <main>
      {@render children()}
    </main>
    
    <footer>Footer</footer>

SvelteKit データ & フォーム

ロード関数

  • サーバー/クライアントでのデータ取得です。

    // +page.ts (universal: server + client)
    import type { PageLoad } from './$types';
    
    export const load: PageLoad = async ({
      params, fetch
    }) => {
      const res = await fetch(
        `/api/posts/${params.slug}`
      );
      return { post: await res.json() };
    };
    
    // +page.server.ts (server only)
    import type { PageServerLoad } from './$types';
    import { db } from '$lib/server/database';
    
    export const load: PageServerLoad = async ({
      params, cookies
    }) => {
      const post = await db.getPost(params.slug);
      return { post };
    };
    <!-- +page.svelte -->
    <script>
      let { data } = $props();
    </script>
    <h1>{data.post.title}</h1>
    <p>{data.post.content}</p>

フォームアクション

  • サーバーサイドのフォーム処理です。プログレッシブエンハンスメント対応。

    // +page.server.ts
    import { fail, redirect } from '@sveltejs/kit';
    
    export const actions = {
      login: async ({ request, cookies }) => {
        const data = await request.formData();
        const email = data.get('email');
        const password = data.get('password');
    
        const user = await authenticate(
          email, password
        );
        if (!user) {
          return fail(401, {
            email, incorrect: true
          });
        }
        cookies.set('session', user.token,
          { path: '/' });
        throw redirect(303, '/dashboard');
      }
    };
    <!-- +page.svelte -->
    <script>
      import { enhance } from '$app/forms';
      let { form } = $props();
    </script>
    <form method="POST" action="?/login"
      use:enhance>
      <input name="email"
        value={form?.email ?? ''} />
      {#if form?.incorrect}
        <p class="error">Invalid credentials</p>
      {/if}
      <button>Log in</button>
    </form>

APIルート

  • +server.ts でREST APIエンドポイントを作成します。

    // src/routes/api/posts/+server.ts
    import { json, error } from '@sveltejs/kit';
    import type { RequestHandler } from './$types';
    
    export const GET: RequestHandler = async ({
      url
    }) => {
      const limit = Number(
        url.searchParams.get('limit') ?? 10
      );
      const posts = await db.getPosts(limit);
      return json(posts);
    };
    
    export const POST: RequestHandler = async ({
      request
    }) => {
      const data = await request.json();
      const post = await db.createPost(data);
      return json(post, { status: 201 });
    };

引用・参考リンク

Related Cheatsheets

Related Goods

  • Svelteの基本概念からコンポーネント設計まで体系的に学べる入門書。
リアクティビティの仕組みも丁寧に解説しています。
    Svelteの基本概念からコンポーネント設計まで体系的に学べる入門書。 リアクティビティの仕組みも丁寧に解説しています。
    詳細をみる
  • Svelteを最短で習得したい方に最適な一冊。
基本から実践まで、コンパクトにまとめられています。
    Svelteを最短で習得したい方に最適な一冊。 基本から実践まで、コンパクトにまとめられています。
    詳細をみる
  • 日本で一番売れているJavaScript本です。
2023年に大幅改訂されました。
    日本で一番売れているJavaScript本です。 2023年に大幅改訂されました。
    詳細をみる
  • ケーブルに取り付け可能なTypeCとLightningの変換アダプタです。
スタイリッシュなデザインで、Apple製品との相性抜群です。
    ケーブルに取り付け可能なTypeCとLightningの変換アダプタです。 スタイリッシュなデザインで、Apple製品との相性抜群です。
    詳細をみる
  • お気に入りのサウンドデバイスをすぐ取り出せる位置にディスプレイさせておくことができます。
    お気に入りのサウンドデバイスをすぐ取り出せる位置にディスプレイさせておくことができます。
    詳細をみる

WebTerm - Recommended tools

WebTermは、ブラウザでLinuxコマンド・Gitコマンドを安全に実行でき、チュートリアル式で学べるターミナルサンドボックスです。
AIコーディングツールの普及に伴い、CLIの基礎知識を身につける重要性は増しています。実際のターミナルを操作するのに抵抗がある方でも、WebTermはローカル環境を壊す心配がありません。「会員登録不要・無料」で利用でき、学習環境として最適です。

WebTerm Logo

WebTerm

Browser Terminal Sandbox for Learning CLI

開く

All Cheatsheets

エンジニア・プログラマー向けの便利なチートシートを多数まとめています(SP/Tablet/PC対応)
すべてのチートシートを見る