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 9: ifブロックで条件付きレンダリング

Last updated at Posted at 2025-12-08

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

前回のおさらい

Day 8では、forブロックとループコンテキストを学びました。今日は、条件によってUIの表示/非表示を制御する「ifブロック」を詳しく見ていきます。

ifブロックの基本

ifブロックは、指定した条件が真のときだけUIを描画する構文です。

基本的な使い方

{{ if:user.isLoggedIn }}
  <div class="welcome">
    <p>ようこそ、{{ user.name }}さん!</p>
    <button data-bind="onclick:logout">ログアウト</button>
  </div>
{{ endif: }}
export default class {
  user = {
    name: "Alice",
    isLoggedIn: true  // boolean値
  };
  
  logout() {
    this.user.isLoggedIn = false;
  }
}

動作:

  • user.isLoggedIntrueのとき → ブロック内が描画される
  • user.isLoggedInfalseのとき → ブロック内が削除される

構文の要素

{{ if:条件パス }}
  <!-- 条件が真のときに表示される内容 -->
{{ endif: }}

else と elseif

ifブロックはelseelseifをサポートしています。

else の使い方

{{ if:user.isLoggedIn }}
  <div class="user-panel">
    <p>ようこそ、{{ user.name }}さん</p>
    <button data-bind="onclick:logout">ログアウト</button>
  </div>
{{ else: }}
  <div class="login-prompt">
    <p>ログインしてください</p>
    <button data-bind="onclick:showLogin">ログイン</button>
  </div>
{{ endif: }}

動作:

  • user.isLoggedIntrue → 上のブロックを表示
  • user.isLoggedInfalse → 下のブロック(else)を表示

elseif の使い方

{{ if:user.role.isAdmin }}
  <div class="admin-panel">
    <h2>管理者パネル</h2>
    <button data-bind="onclick:manageUsers">ユーザー管理</button>
  </div>
{{ elseif:user.role.isModerator }}
  <div class="moderator-panel">
    <h2>モデレーターパネル</h2>
    <button data-bind="onclick:reviewContent">コンテンツ審査</button>
  </div>
{{ else: }}
  <div class="user-panel">
    <h2>一般ユーザー</h2>
    <p>閲覧のみ可能です</p>
  </div>
{{ endif: }}
export default class {
  user = {
    role: {
      isAdmin: false,
      isModerator: true,
      isUser: true
    }
  };
}

動作の流れ:

  1. user.role.isAdmintrue → 管理者パネルを表示
  2. そうでなく、user.role.isModeratortrue → モデレーターパネルを表示
  3. どちらもfalse → 一般ユーザーパネルを表示

複数の elseif

{{ if:status.isPending }}
  <span class="badge pending">保留中</span>
{{ elseif:status.isApproved }}
  <span class="badge approved">承認済み</span>
{{ elseif:status.isRejected }}
  <span class="badge rejected">却下</span>
{{ else: }}
  <span class="badge unknown">不明</span>
{{ endif: }}

条件の評価:boolean値が必須

重要: このフレームワークでは、条件は必ずboolean型でなければなりません。

export default class {
  // ✅ 正しい - boolean値
  isLoggedIn = true;
  hasPermission = false;
  
  // ❌ 間違い - 文字列や数値は使えない
  status = "active";  // if:statusはエラー
  count = 5;          // if:countはエラー
  name = "Alice";     // if:nameはエラー
}

なぜboolean値が必須なのか?

明示性と安全性のためです:

  • 条件の意図が明確になる
  • 意図しない型変換によるバグを防ぐ
  • コードの可読性が向上する

フィルターでboolean値に変換

boolean値でない値を条件として使いたい場合、フィルターを使って変換します。

truthyフィルター

値が「真と評価される」かをbooleanに変換します:

{{ if:items.length|truthy }}
  <p>アイテムがあります({{ items.length }}件)</p>
{{ else: }}
  <p>アイテムがありません</p>
{{ endif: }}
export default class {
  items = [1, 2, 3];  // lengthは3(数値)
}

変換される値:

  • truthyフィルター: 0, "", null, undefinedfalse、それ以外 → true

falseyフィルター

値が「偽と評価される」かをbooleanに変換します:

{{ if:errorMessage|falsey }}
  <p>エラーはありません</p>
{{ else: }}
  <p class="error">{{ errorMessage }}</p>
{{ endif: }}
export default class {
  errorMessage = "";  // 空文字列
}

変換される値:

  • falseyフィルター: 0, "", null, undefinedtrue、それ以外 → false

実用例:配列の長さチェック

<div class="cart">
  {{ if:cart.items.length|truthy }}
    <h3>カート内のアイテム ({{ cart.items.length }})</h3>
    <ul>
      {{ for:cart.items }}
        <li>{{ cart.items.*.name }}</li>
      {{ endfor: }}
    </ul>
  {{ else: }}
    <p class="empty">カートは空です</p>
  {{ endif: }}
</div>
export default class {
  cart = {
    items: []
  };
}

派生状態(getter)での条件定義

複雑な条件は、getterでboolean値として定義するのがベストプラクティスです。

基本パターン

{{ if:canCheckout }}
  <button data-bind="onclick:checkout" class="btn-primary">
    購入する
  </button>
{{ else: }}
  <p class="error">{{ checkoutError }}</p>
{{ endif: }}
export default class {
  cart = {
    items: [],
    totalPrice: 0
  };
  user = {
    isLoggedIn: false
  };
  
  get canCheckout() {
    // 複雑な条件をbooleanで返す
    return this.user.isLoggedIn && 
           this.cart.items.length > 0 &&
           this.cart.totalPrice > 0;
  }
  
  get checkoutError() {
    if (!this.user.isLoggedIn) {
      return "ログインが必要です";
    }
    if (this.cart.items.length === 0) {
      return "カートが空です";
    }
    return "最低購入金額を満たしていません";
  }
}

利点:

  • UIがシンプルになる
  • ビジネスロジックが集約される
  • テストしやすい

ループコンテキスト内での派生状態

{{ for:users }}
  <div class="user-card">
    <h3>{{ users.*.name }}</h3>
    
    {{ if:users.*.canEdit }}
      <button data-bind="onclick:editUser">編集</button>
    {{ endif: }}
    
    {{ if:users.*.isActive }}
      <span class="badge active">アクティブ</span>
    {{ else: }}
      <span class="badge inactive">非アクティブ</span>
    {{ endif: }}
  </div>
{{ endfor: }}
export default class {
  users = [
    { name: "Alice", role: "admin", lastLogin: Date.now() },
    { name: "Bob", role: "user", lastLogin: Date.now() - 86400000 * 10 }
  ];
  currentUser = {
    role: "admin"
  };
  
  get "users.*.canEdit"() {
    return this.currentUser.role === "admin" || 
           this["users.*.role"] === "admin";
  }
  
  get "users.*.isActive"() {
    const lastLogin = this["users.*.lastLogin"];
    const daysSinceLogin = (Date.now() - lastLogin) / (86400000);
    return daysSinceLogin < 7;  // 7日以内がアクティブ
  }
}

DOMの追加と削除

ifブロックは、条件が変わったときにDOMを追加/削除します。

プレースホルダーの仕組み

<!-- 初期状態(条件が偽) -->
<div id="container">
  <!--if-block-->
</div>

<!-- 条件が真になったとき -->
<div id="container">
  <!--if-block-->
  <div class="content">表示される内容</div>
</div>

<!-- 条件が偽に戻ったとき -->
<div id="container">
  <!--if-block-->
</div>

コメントノード(<!--if-block-->)がプレースホルダーとして残り、「どこに要素を戻すか」を記憶します。

実践例:ステータス管理

ifブロックとelse/elseifを使った実用的な例:

<template>
  <div class="order-status">
    <h2>注文状況</h2>
    
    {{ if:order.status.isPending }}
      <div class="status pending">
        <h3>⏳ 処理中</h3>
        <p>ご注文を処理しています</p>
        <button data-bind="onclick:cancelOrder">注文をキャンセル</button>
      </div>
    {{ elseif:order.status.isShipped }}
      <div class="status shipped">
        <h3>🚚 発送済み</h3>
        <p>追跡番号: {{ order.trackingNumber }}</p>
        <a data-bind="attr.href:trackingUrl" target="_blank">配送状況を確認</a>
      </div>
    {{ elseif:order.status.isDelivered }}
      <div class="status delivered">
        <h3>✅ 配達完了</h3>
        <p>{{ order.deliveredDate }}に配達されました</p>
        
        {{ if:order.canReview }}
          <button data-bind="onclick:writeReview">レビューを書く</button>
        {{ else: }}
          <p>レビューありがとうございました</p>
        {{ endif: }}
      </div>
    {{ elseif:order.status.isCancelled }}
      <div class="status cancelled">
        <h3>❌ キャンセル済み</h3>
        <p>この注文はキャンセルされました</p>
      </div>
    {{ else: }}
      <div class="status unknown">
        <h3>⚠️ ステータス不明</h3>
        <p>サポートにお問い合わせください</p>
      </div>
    {{ endif: }}
    
    {{ if:order.hasNotes|truthy }}
      <div class="notes">
        <h4>メモ</h4>
        <p>{{ order.notes }}</p>
      </div>
    {{ endif: }}
  </div>
</template>

<script type="module">
export default class {
  order = {
    id: "ORD-12345",
    trackingNumber: "TRK-9876543210",
    deliveredDate: "2024-12-01",
    notes: "玄関前に置いてください",
    hasReview: false,
    status: {
      isPending: false,
      isShipped: false,
      isDelivered: true,
      isCancelled: false
    }
  };
  
  get canReview() {
    return this.order.status.isDelivered && !this.order.hasReview;
  }
  
  get trackingUrl() {
    return `https://tracking.example.com/${this.order.trackingNumber}`;
  }
  
  get hasNotes() {
    return this.order.notes.length > 0;
  }
  
  cancelOrder() {
    if (confirm("本当にキャンセルしますか?")) {
      this.order.status = {
        isPending: false,
        isShipped: false,
        isDelivered: false,
        isCancelled: true
      };
    }
  }
  
  writeReview() {
    console.log("レビュー画面を開く");
  }
}
</script>

この例では:

  • 複数のステータスをelseifで分岐
  • ネストしたifでさらに条件分岐
  • truthyフィルターで文字列の存在チェック
  • getterで複雑な条件を定義

パフォーマンスの考慮

頻繁に切り替わる場合

<!-- ❌ パフォーマンスが悪い可能性 -->
{{ if:isVisible }}
  <div class="heavy-component">
    <!-- 複雑なコンテンツ -->
  </div>
{{ endif: }}

毎回DOM要素を削除・再作成するとコストが高い場合があります。

CSSで制御する代替案

<!-- ✅ 表示/非表示だけならCSSが効率的 -->
<div data-bind="attr.hidden:isHidden" class="heavy-component">
  <!-- 複雑なコンテンツ -->
</div>
export default class {
  isVisible = true;
  
  get isHidden() {
    return !this.isVisible;
  }
}

使い分け:

  • ifブロック: コンテンツが不要なときは完全に削除したい場合
  • CSS(hidden属性): 頻繁に切り替わる、または初期化コストが高い場合

まとめ

今日は、ifブロックで条件付きレンダリングを学びました:

ifブロックの構文:

  • {{ if:条件 }}...{{ endif: }}
  • {{ if:条件 }}...{{ else: }}...{{ endif: }}
  • {{ if:条件 }}...{{ elseif:条件 }}...{{ else: }}...{{ endif: }}

重要なルール:

  • 条件は必ずboolean型
  • boolean以外はtruthy/falseyフィルターで変換
  • 複雑な条件はgetterで定義

ベストプラクティス:

  • ビジネスロジックをgetterに集約
  • UIはシンプルに保つ
  • 頻繁に切り替わる場合はCSSを検討

次回予告:
明日は、「属性バインディングとイベントハンドラ」を詳しく解説します。data-bind属性を使った様々なバインディング方法と、イベントハンドラの引数について学びます。


次回: Day 10「属性バインディングとイベントハンドラ」

ifブロックについて質問があれば、コメントでぜひ!

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?