0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Day 4: ボイラープレートを排除する仕組み

Last updated at Posted at 2025-12-03

この記事は「構造パスで作るシンプルなフロントエンドフレームワーク(Structive)」Advent Calendarの4日目です。
Structiveについて詳しくはこちらより

前回のおさらい

3日目では、「UIと状態は同じデータの異なる表出」という核心的な思想を学びました。今日は、この思想がどのようにボイラープレートの排除につながるのか、技術的な観点から解説します。

ボイラープレートとは?

ボイラープレートとは、本質的なロジックではないが、フレームワークを使うために書かざるを得ない「定型的なコード」のことです。

典型的なボイラープレートの例

Reactで簡単なカウンターを作る場合:

import { useState } from 'react';

function Counter() {
  // ボイラープレート1: useState の呼び出し
  const [count, setCount] = useState(0);
  
  // ボイラープレート2: イベントハンドラの定義
  const increment = () => {
    setCount(count + 1);
  };
  
  const decrement = () => {
    setCount(count - 1);
  };
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

やりたいこと: カウンターの値を表示して増減させる

書かされるコード:

  • useStateのインポートと呼び出し
  • setter関数(setCount
  • 2つのイベントハンドラ関数

実際のロジックは「数値を±1する」だけなのに、多くの定型コードが必要です。

構造パスによる解決

同じカウンターを構造パスで書くと:

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button data-bind="onclick:increment">+</button>
    <button data-bind="onclick:decrement">-</button>
  </div>
</template>

<script type="module">
export default class {
  count = 0;
  
  increment() {
    this.count += 1;
  }
  
  decrement() {
    this.count -= 1;
  }
}
</script>

不要になったもの:

  • useStateの呼び出し
  • setter関数(setCount
  • ラッパー関数

なぜこれが可能なのか? それを順を追って見ていきましょう。

1. 宣言的なUIの記述

従来の命令的なアプローチ

従来、JavaScriptでUIを更新するには、DOMを直接操作する必要がありました:

// 命令的:「どうやって」変更するかを記述
const countElement = document.querySelector('#count');
countElement.textContent = count;

const button = document.querySelector('#increment-btn');
button.addEventListener('click', () => {
  count += 1;
  countElement.textContent = count; // 手動で更新
});

問題点:

  • DOMの取得と操作が煩雑
  • 状態とUIの同期を手動で管理
  • コードが冗長で読みにくい

宣言的なアプローチ

構造パスでは、UIが「どう見えるべきか」だけを記述します:

<p>Count: {{ count }}</p>

ポイント:

  • countという値を表示する」と宣言するだけ
  • 「どうやって更新するか」は書かない
  • フレームワークが自動的に更新を管理

2. 状態の直接操作

なぜsetter関数が不要なのか?

従来のフレームワークでsetter関数が必要な理由:

const [count, setCount] = useState(0);

// これは動かない
count = 5;  // ❌ 直接変更してもUIは更新されない

// setter関数を使う必要がある
setCount(5);  // ✅ フレームワークに「更新があった」と通知

setter関数は、フレームワークに「状態が変わった」ことを伝えるための仕組みです。

構造パスのアプローチ

構造パスでは、状態を直接変更するだけでOKです:

this.count = 5;  // ✅ これだけでUIが更新される

なぜ可能なのか?

フレームワークは、状態オブジェクトをProxyでラップしています。Proxyは、プロパティの変更を自動的に検知できます(詳細は5日目で解説)。

// フレームワーク内部(概念的なイメージ)
const state = new Proxy(yourStateObject, {
  set(target, property, value) {
    target[property] = value;
    // 自動的にUIを更新
    updateUI(property);
    return true;
  }
});

開発者は何も意識せず、純粋なJavaScriptとして状態を操作できます。

3. 双方向バインディングの自動化

従来のフォーム処理

入力フォームを扱う場合、従来は大量のコードが必要でした:

function Form() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  
  // ボイラープレート:各フィールドごとにハンドラ
  const handleNameChange = (e) => {
    setName(e.target.value);
  };
  
  const handleEmailChange = (e) => {
    setEmail(e.target.value);
  };
  
  return (
    <form>
      <input 
        value={name} 
        onChange={handleNameChange} 
      />
      <input 
        value={email} 
        onChange={handleEmailChange} 
      />
    </form>
  );
}

フィールドが10個あれば、10個のハンドラが必要です。

構造パスの自動バインディング

<template>
  <form>
    <input data-bind="value: user.name">
    <input data-bind="value: user.email">
  </form>
</template>

<script type="module">
export default class {
  user = {
    name: "",
    email: ""
  };
}
</script>

イベントハンドラは不要です。

data-bind="value: user.name"と書くだけで:

  1. 状態の値がinputに反映される(状態 → UI)
  2. ユーザーの入力が状態に反映される(UI → 状態)

フレームワークが自動的に双方向のバインディングを設定します。

4. イベントハンドラの簡潔な記述

従来のイベント処理

function ProductList({ products }) {
  const handleDelete = (productId) => {
    // 削除処理
  };
  
  return (
    <ul>
      {products.map(product => (
        <li key={product.id}>
          {product.name}
          {/* ラッパー関数が必要 */}
          <button onClick={() => handleDelete(product.id)}>
            Delete
          </button>
        </li>
      ))}
    </ul>
  );
}

各アイテムにイベントハンドラをバインドするために、アロー関数でラップする必要があります。

構造パスのイベント処理

<template>
  <ul>
    {{ for:products }}
      <li>
        {{ products.*.name }}
        <button data-bind="onclick:deleteProduct">Delete</button>
      </li>
    {{ endfor: }}
  </ul>
</template>

<script type="module">
export default class {
  products = [...];
  
  deleteProduct(event, index) {
    // index は自動的に渡される
    this.products = this.products.toSpliced(index, 1);
  }
}
</script>

ラッパー関数は不要です。

フレームワークがループコンテキストを自動的に追跡し、クリックされたアイテムのインデックスをdeleteProductメソッドに渡します。

ボイラープレート排除の実例比較

実際のアプリケーションで、どれだけコードが削減されるか見てみましょう。

例:商品カートの数量変更

React(24行):

function CartItem({ item, onUpdateQuantity }) {
  const [quantity, setQuantity] = useState(item.quantity);
  
  const handleChange = (e) => {
    const newQuantity = parseInt(e.target.value);
    setQuantity(newQuantity);
  };
  
  const handleBlur = () => {
    onUpdateQuantity(item.id, quantity);
  };
  
  return (
    <div>
      <span>{item.name}</span>
      <input 
        type="number" 
        value={quantity}
        onChange={handleChange}
        onBlur={handleBlur}
      />
    </div>
  );
}

構造パス(17行):

<template>
  {{ for:cart.items }}
    <div>
      <span>{{ cart.items.*.name }}</span>
      <input 
        type="number" 
        data-bind="valueAsNumber: cart.items.*.quantity"
      >
    </div>
  {{ endfor: }}
</template>

<script type="module">
export default class {
  cart = { items: [...] };
}
</script>

約3分の2のコード量で同じ機能を実現。

なぜボイラープレートが不要になるのか?

まとめると、以下の3つの仕組みが連携しています:

1. 構造パスによる統一的なアクセス

UIと状態が同じ構造パスを共有することで、明示的なマッピングが不要。

2. Proxyによる自動検知

状態の変更を自動的に検知し、UIを更新。setter関数が不要。

3. フレームワークの賢い推論

  • value属性 → 双方向バインディング
  • onclick属性 → ループインデックスの自動渡し
  • forブロック → ループコンテキストの自動管理

開発者が書くコードは「本質的なロジック」だけです。

開発者体験の向上

ボイラープレートの排除は、単なる「コード量の削減」以上の意味があります:

1. 認知負荷の低減

考えることが減ります:

  • setter関数の名前を考えなくていい
  • イベントハンドラをどう書くか悩まなくていい
  • 状態の更新をどう伝播させるか考えなくていい

2. バグの削減

書くコードが少ない = バグが入る場所が少ない:

  • setter関数の呼び忘れがない
  • イベントハンドラのバインディングミスがない
  • 状態の不整合が起きにくい

3. 学習コストの低減

覚えることが少ない:

  • useStateuseEffectなどのAPIを覚える必要がない
  • ライフサイクルメソッドを理解する必要がない
  • 純粋なJavaScriptの知識だけでOK

まとめ

今日は、構造パスがどのようにボイラープレートを排除するか学びました:

排除される主なボイラープレート:

  • useState の呼び出し
  • setter関数
  • イベントハンドラのラッパー関数
  • 手動のDOM操作

実現する仕組み:

  • 宣言的なUI記述
  • 状態の直接操作(Proxy)
  • 自動的な双方向バインディング
  • フレームワークの賢い推論

開発者への利点:

  • コード量の削減(約1/3)
  • 認知負荷の低減
  • バグの削減
  • 学習コストの低減

次回予告:
明日は、「仮想DOMなしでリアクティビティを実現する方法」を解説します。構造パスとProxyを使って、どのように効率的なUI更新を実現するのか、技術的な詳細に踏み込みます。


次回: Day 5「仮想DOMなしでリアクティビティを実現する方法」

ボイラープレート排除について質問があれば、コメントでぜひ!

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?