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 3: UIと状態を同じ構造で表現する思想

Last updated at Posted at 2025-12-02

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

前回のおさらい

2日目では、構造パスの基本的な仕組みを学びました:

  • 構造パスはデータへの「住所」
  • ドット記法でネストを表現
  • ワイルドカード(*)で配列を抽象化

今日は、このフレームワークの核心となる思想「UIと状態は同じデータの異なる表出」について深く掘り下げます。

従来のアプローチの問題点

まず、従来のフレームワークでUIと状態がどう扱われているか見てみましょう。

Reactの例

// 状態の定義
const [user, setUser] = useState({
  profile: {
    name: "Alice",
    email: "alice@example.com"
  }
});

// UIの定義
return (
  <div>
    <h2>{user.profile.name}</h2>
    <p>{user.profile.email}</p>
  </div>
);

// 状態の更新
setUser({
  ...user,
  profile: {
    ...user.profile,
    name: "Bob"
  }
});

問題点:

  1. 状態とUIが異なる形式 - JSXとJavaScriptオブジェクトは別物
  2. 更新に中間層 - setUserという関数を経由する必要がある
  3. 不変性の維持 - スプレッド構文で元のオブジェクトをコピー

つまり、状態とUIは「別々のもの」として扱われています。

新しい思想:UIと状態は同じもの

このフレームワークでは、まったく異なる考え方をします。

UIと状態は、同じデータの異なる表出である

これは、以下を意味します:

  • UIは状態を「見る窓」に過ぎない
  • 状態が変わればUIも変わる
  • 両者は同じ構造を共有する

具体例で理解する

// 状態の定義
class State {
  user = {
    profile: {
      name: "Alice",
      email: "alice@example.com"
    }
  };
}
<!-- UIの定義 -->
<div>
  <h2>{{ user.profile.name }}</h2>
  <p>{{ user.profile.email }}</p>
</div>

注目してください:

  • UIの{{ user.profile.name }}
  • 状態のuser.profile.name

まったく同じ構造パスを使っています。

状態の更新もシンプル

// 状態の更新
this["user.profile.name"] = "Bob";

setter関数も、スプレッド構文も不要です。純粋なJavaScriptオブジェクトとして構造パスで直接変更するだけです。

単一の真実の源(Single Source of Truth)

この思想の最大のメリットは、データが一箇所にしか存在せず、そのデータの位置は構造パスにより一意に特定されることです。
UIでも状態でも、同じ構造パスは同じデータを参照すること保証します。

可読性と保守性の向上

UIの依存関係が一目瞭然

構造パスを使うと、UIがどのデータに依存しているかが明確です:

<div class="product-card">
  <h3>{{ product.name }}</h3>
  <p>{{ product.description }}</p>
  <span class="price">${{ product.price }}</span>
  <button data-bind="onclick:addToCart">Add to Cart</button>
</div>

このUIを見れば、以下がすぐにわかります:

  • product.nameに依存
  • product.descriptionに依存
  • product.priceに依存
  • addToCartメソッドを呼び出す

影響範囲の追跡が簡単

状態を変更するとき、どのUIが影響を受けるか簡単にわかります:

// product.price を変更する
this["product.price"] = 1999;

// 影響を受けるUI: {{ product.price }} を使っている箇所
// エディタで「product.price」を検索するだけ!

従来のフレームワークでは、propsの連鎖やコンテキストを追う必要がありますが、構造パスなら単純な文字列検索で完結します。

具体例:ショッピングカート

実際のアプリケーションで、この思想がどう機能するか見てみましょう。

状態の定義

class State {
  cart = {
    items: [
      { productId: 1, quantity: 2 },
      { productId: 5, quantity: 1 }
    ]
  };
  
  products = [
    { id: 1, name: "Laptop", price: 999 },
    { id: 2, name: "Mouse", price: 29 },
    // ...
  ];
}

UIテンプレート

<table>
  <thead>
    <tr>
      <th>Product</th>
      <th>Quantity</th>
      <th>Price</th>
    </tr>
  </thead>
  <tbody>
    {{ for:cart.items }}
    <tr>
      <td>{{ cart.items.*.product.name }}</td>
      <td>
        <input type="number" data-bind="valueAsNumber: cart.items.*.quantity">
      </td>
      <td>${{ cart.items.*.price }}</td>
    </tr>
    {{ endfor: }}
  </tbody>
</table>

何が起きているか?

  1. UIは状態を描画しているだけ

    • {{ cart.items.*.product.name }} → 商品名を表示
    • {{ cart.items.*.quantity }} → 数量を表示
    • {{ cart.items.*.price }} → 価格を表示
  2. 状態とUIは同じ構造

    • UIの構造パスと状態のプロパティが完全に一致
    • 中間的な変換やマッピングが不要
  3. 双方向バインディングも自然

    • <input data-bind="valueAsNumber: cart.items.*.quantity">
    • ユーザーが入力 → 状態が更新 → UIが更新

スケーラビリティへの影響

この思想は、大規模アプリケーションでも威力を発揮します。

機能追加が簡単

新しい機能を追加するとき:

// 1. 状態にプロパティを追加
class State {
  cart = {
    items: [...],
    discount: 0,  // 新しい機能:割引
    couponCode: ""
  };
}
<!-- 2. UIに対応する要素を追加 -->
<div>
  <input data-bind="value: cart.couponCode" placeholder="Coupon Code">
  <p>Discount: {{ cart.discount }}%</p>
</div>

それだけです。 setter関数の定義も、イベントハンドラの追加も不要です。

チーム開発での共通言語

構造パスは、チームメンバー間の共通言語になります:

デザイナー: 「この商品名の部分、フォントを変えたいです」
開発者: 「{{ product.name }}の部分ですね。CSSで.product-nameクラスを追加します」

バックエンド: 「APIのレスポンスにuser.isVerifiedを追加しました」
フロントエンド: 「了解。{{ user.isVerified }}でUIに表示します」

構造パスという明確な「住所」があるため、コミュニケーションがスムーズになります。

3つの原則

この思想をまとめると、以下の3つの原則になります:

1. 同じ構造の原則

UIと状態は同じデータ構造を持つ。両者は同じ構造パスでアクセス可能。

2. 単一の真実の原則

状態は一箇所にのみ存在する(Single Source of Truth)。UIは常にその状態を反映する。

3. 直接操作の原則

状態を変更するのに中間層は不要。純粋なJavaScriptオブジェクトとして直接操作する。

まとめ

今日は、このフレームワークの核心的な思想を学びました:

UIと状態は同じデータの異なる表出:

  • UIは状態を「見る窓」
  • 両者は同じ構造パスを共有
  • データは常に一箇所にのみ存在

この思想がもたらす利点:

  • 可読性の向上(依存関係が明確)
  • 保守性の向上(影響範囲が追跡しやすい)
  • スケーラビリティ(機能追加が簡単)
  • チーム開発の効率化(共通言語)

次回予告:
明日は、「ボイラープレートを排除する仕組み」について解説します。setter関数やイベントハンドラが不要になる理由を、技術的な観点から掘り下げます。


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

この思想について、疑問や意見があればコメントで教えてください!

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?