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 12: パイプフィルターの活用

0
Last updated at Posted at 2025-12-11

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

前回のおさらい

Day 11では、派生状態(getter)を学びました。今日は、値の変換やフォーマットを行う「パイプフィルター」を詳しく見ていきます。

パイプフィルターとは?

パイプフィルターは、構造パスの値を変換・整形するための機能です。

基本構文

{{ パス|フィルター名 }}

例:

<p>{{ user.name|uc }}</p>
<!-- "alice" → "ALICE" -->

<p>${{ product.price|fix,2 }}</p>
<!-- 999 → "999.00" -->

パイプ(|)の意味

パイプは「値を次の処理に渡す」という意味です:

値 → フィルター1 → フィルター2 → 結果

フィルターの特徴

1. 純粋関数

フィルターは純粋関数として実装されています:

  • 同じ入力には常に同じ出力
  • 副作用なし
  • 状態を持たない
// ✅ 純粋関数
function uc(value) {
  return value.toUpperCase();
}

// ❌ 状態を持つことはできない
let counter = 0;
function withState(value) {
  return value + counter++;  // NG
}

2. 単一値の変換

フィルターは単一の値だけを変換します。複数のパス値を合成することはできません。

<!-- ✅ OK: 単一値の変換 -->
{{ user.name|uc }}

<!-- ❌ NG: 複数値の合成はできない -->
{{ user.firstName + user.lastName|uc }}

複数値を扱いたい場合は、getterを使います:

get fullName() {
  return `${this.firstName} ${this.lastName}`;
}
{{ fullName|uc }}

3. パラメータ

フィルターはパラメータを持てます。カンマ(,)で区切ります:

{{ value|fix,2 }}
<!-- 小数点以下2桁 -->

{{ text|slice,0,10 }}
<!-- 0文字目から10文字目まで -->

{{ number|round,1 }}
<!-- 小数点第1位で四捨五入 -->

4. チェーン

複数のフィルターをパイプで連結できます:

{{ text|trim|uc }}
<!-- 1. 前後の空白を削除 → 2. 大文字に変換 -->

{{ price|fix,2|defaults,0.00 }}
<!-- 1. 小数点2桁 → 2. falsyなら0.00を使用 -->

フィルターの種類

比較フィルター

値を比較してboolean値を返します。

eq / ne(等価・不等価)

{{ if:status|eq,active }}
  <span class="badge active">アクティブ</span>
{{ endif: }}

{{ if:user.role|ne,admin }}
  <p>管理者権限がありません</p>
{{ endif: }}
export default class {
  status = "active";
  user = { role: "user" };
}

not(否定)

{{ if:user.isLoggedIn|not }}
  <a href="/login">ログイン</a>
{{ endif: }}

数値比較フィルター

数値の大小を比較します。

lt / le / gt / ge(未満・以下・より大きい・以上)

{{ if:product.stock|lt,5 }}
  <span class="warning">残りわずか</span>
{{ endif: }}

{{ if:user.age|ge,18 }}
  <p>成人向けコンテンツを表示</p>
{{ endif: }}

{{ if:score|gt,80 }}
  <span class="grade-a">A評価</span>
{{ elseif:score|gt,60 }}
  <span class="grade-b">B評価</span>
{{ else: }}
  <span class="grade-c">C評価</span>
{{ endif: }}

算術フィルター

数値演算を行います。

inc / dec / mul / div / mod

<!-- 加算 -->
<li>{{ $1|inc,1 }}. {{ items.*.name }}</li>
<!-- インデックス0を1始まりに -->

<!-- 減算 -->
<p>割引後: ${{ price|mul,0.9 }}</p>
<!-- 10%オフ -->

<!-- 乗算・除算 -->
<p>半額: ${{ price|div,2 }}</p>

<!-- 余り -->
{{ if:$1|mod,2|eq,0 }}
  <tr class="even">...</tr>
{{ else: }}
  <tr class="odd">...</tr>
{{ endif: }}

数値フォーマットフィルター

数値を整形して表示します。

fix(小数点固定)

<p>価格: ${{ product.price|fix,2 }}</p>
<!-- 999 → "999.00" -->
<!-- 29.5 → "29.50" -->

locale(地域化された数値)

<p>金額: {{ amount|locale }}円</p>
<!-- 1234567 → "1,234,567" -->

percent(パーセント表示)

<p>進捗: {{ progress|percent }}%</p>
<!-- 0.856 → "86%" -->

<p>進捗: {{ progress|percent,1 }}%</p>
<!-- 0.856 → "85.6%" (小数第1位まで) -->

round / floor / ceil(丸め)

<!-- 四捨五入 -->
<p>{{ value|round }}</p>
<!-- 3.7 → 4 -->

<!-- 小数第1位で四捨五入 -->
<p>{{ value|round,1 }}</p>
<!-- 3.67 → 3.7 -->

<!-- 切り捨て -->
<p>{{ value|floor }}</p>
<!-- 3.9 → 3 -->

<!-- 切り上げ -->
<p>{{ value|ceil }}</p>
<!-- 3.1 → 4 -->

文字列フィルター

文字列を変換・整形します。

uc / lc / cap(大文字・小文字変換)

<h2>{{ product.name|uc }}</h2>
<!-- "laptop" → "LAPTOP" -->

<p>{{ user.email|lc }}</p>
<!-- "Alice@EXAMPLE.COM" → "alice@example.com" -->

<p>{{ title|cap }}</p>
<!-- "hello world" → "Hello world" -->

trim(空白削除)

<p>{{ input|trim }}</p>
<!-- "  hello  " → "hello" -->

slice / substr(部分文字列)

<!-- 最初の10文字 -->
<p>{{ description|slice,0,10 }}...</p>

<!-- 3文字目から5文字分 -->
<p>{{ text|substr,3,5 }}</p>

pad(パディング)

<!-- 5桁に0埋め -->
<p>注文番号: {{ orderId|pad,5,0 }}</p>
<!-- 123 → "00123" -->

rep(繰り返し)

<p>{{ star|rep,5 }}</p>
<!-- "★" → "★★★★★" -->

rev(逆順)

<p>{{ text|rev }}</p>
<!-- "hello" → "olleh" -->

型変換フィルター

int / float(数値変換)

<p>{{ stringNumber|int }}</p>
<!-- "123" → 123 -->

<p>{{ stringFloat|float }}</p>
<!-- "3.14" → 3.14 -->

boolean / number / string(型変換)

{{ if:value|boolean }}
  <p>真です</p>
{{ endif: }}

日付フィルター

Date型の値を整形します。

date / time / datetime

<p>日付: {{ order.createdAt|date }}</p>
<!-- "2024/12/12" -->

<p>時刻: {{ order.createdAt|time }}</p>
<!-- "14:30:00" -->

<p>日時: {{ order.createdAt|datetime }}</p>
<!-- "2024/12/12 14:30:00" -->

ymd(YYYY-MM-DD形式)

<p>{{ date|ymd }}</p>
<!-- "2024-12-12" -->

<p>{{ date|ymd,/ }}</p>
<!-- "2024/12/12" (区切りを変更) -->
export default class {
  order = {
    createdAt: new Date("2024-12-12T14:30:00")
  };
}

真偽値判定フィルター

truthy / falsy

{{ if:errorMessage|truthy }}
  <p class="error">{{ errorMessage }}</p>
{{ endif: }}

{{ if:items.length|truthy }}
  <p>{{ items.length }}件のアイテム</p>
{{ else: }}
  <p>アイテムがありません</p>
{{ endif: }}

defaults(デフォルト値)

<p>{{ user.nickname|defaults,匿名ユーザー }}</p>
<!-- nicknameが空なら"匿名ユーザー"を表示 -->

<p>{{ product.discount|defaults,0 }}%オフ</p>
<!-- discountが未設定なら0を使用 -->

実践例:商品リスト

フィルターを活用した実用的な例:

<template>
  <div class="product-list">
    <h2>商品一覧</h2>
    
    <div class="stats">
      <p>全{{ products.length }}件</p>
      <p>平均価格: ${{ averagePrice|fix,2 }}</p>
    </div>
    
    <table>
      <thead>
        <tr>
          <th>No.</th>
          <th>商品名</th>
          <th>価格</th>
          <th>在庫</th>
          <th>割引率</th>
          <th>ステータス</th>
        </tr>
      </thead>
      <tbody>
        {{ for:products }}
          <tr data-bind="class.low-stock:products.*.stock|lt,5">
            <td>{{ $1|inc,1 }}</td>
            <td>{{ products.*.name|cap }}</td>
            <td>${{ products.*.price|fix,2 }}</td>
            <td>
              {{ products.*.stock }}
              {{ if:products.*.stock|lt,5 }}
                <span class="badge">残りわずか</span>
              {{ endif: }}
            </td>
            <td>{{ products.*.discount|percent }}%</td>
            <td>
              {{ if:products.*.isAvailable }}
                <span class="status available">販売中</span>
              {{ else: }}
                <span class="status unavailable">販売終了</span>
              {{ endif: }}
            </td>
          </tr>
        {{ endfor: }}
      </tbody>
    </table>
  </div>
</template>

<script type="module">
export default class {
  products = [
    { name: "laptop pro", price: 1299.99, stock: 8, discount: 0.15, isAvailable: true },
    { name: "wireless mouse", price: 29.99, stock: 3, discount: 0, isAvailable: true },
    { name: "keyboard", price: 79.99, stock: 0, discount: 0.25, isAvailable: false },
    { name: "monitor 4k", price: 499.99, stock: 12, discount: 0.1, isAvailable: true }
  ];
  
  get averagePrice() {
    const sum = this.products.reduce((acc, p) => acc + p.price, 0);
    return sum / this.products.length;
  }
}
</script>

この例では:

  • inc,1: インデックスを1始まりに
  • cap: 商品名の先頭を大文字に
  • fix,2: 価格を小数点2桁表示
  • lt,5: 在庫5未満をチェック
  • percent: 割引率をパーセント表示

実践例:ユーザーダッシュボード

複数のフィルターを組み合わせた例:

<template>
  <div class="dashboard">
    <h2>ダッシュボード</h2>
    
    <div class="user-info">
      <h3>{{ user.name|trim|cap }}</h3>
      <p>メール: {{ user.email|lc }}</p>
      <p>登録日: {{ user.registeredAt|date }}</p>
      <p>最終ログイン: {{ user.lastLogin|datetime }}</p>
    </div>
    
    <div class="stats">
      <div class="stat-card">
        <h4>総購入額</h4>
        <p class="amount">${{ user.totalSpent|locale }}</p>
      </div>
      
      <div class="stat-card">
        <h4>ポイント</h4>
        <p class="points">{{ user.points|locale }}pt</p>
      </div>
      
      <div class="stat-card">
        <h4>会員ランク</h4>
        <p class="rank">{{ user.rank|uc }}</p>
      </div>
    </div>
    
    <div class="orders">
      <h3>注文履歴</h3>
      
      {{ if:orders.length|truthy }}
        <table>
          <thead>
            <tr>
              <th>注文番号</th>
              <th>日付</th>
              <th>金額</th>
              <th>ステータス</th>
            </tr>
          </thead>
          <tbody>
            {{ for:orders }}
              <tr>
                <td>{{ orders.*.id|pad,8,0 }}</td>
                <td>{{ orders.*.date|ymd }}</td>
                <td>${{ orders.*.amount|fix,2 }}</td>
                <td>
                  {{ if:orders.*.status|eq,delivered }}
                    <span class="badge success">配達完了</span>
                  {{ elseif:orders.*.status|eq,shipped }}
                    <span class="badge info">配送中</span>
                  {{ else: }}
                    <span class="badge warning">処理中</span>
                  {{ endif: }}
                </td>
              </tr>
            {{ endfor: }}
          </tbody>
        </table>
      {{ else: }}
        <p class="empty">注文履歴がありません</p>
      {{ endif: }}
    </div>
    
    <div class="activity">
      <h3>最近の活動</h3>
      <ul>
        {{ for:activities }}
          <li>
            <span class="time">{{ activities.*.timestamp|time }}</span>
            <span class="text">{{ activities.*.text|trim }}</span>
          </li>
        {{ endfor: }}
      </ul>
    </div>
  </div>
</template>

<script type="module">
export default class {
  user = {
    name: "  alice smith  ",
    email: "ALICE@EXAMPLE.COM",
    registeredAt: new Date("2024-01-15"),
    lastLogin: new Date("2024-12-12T14:30:00"),
    totalSpent: 1234567.89,
    points: 12345,
    rank: "gold"
  };
  
  orders = [
    { id: 123, date: new Date("2024-12-01"), amount: 299.99, status: "delivered" },
    { id: 124, date: new Date("2024-12-08"), amount: 149.99, status: "shipped" },
    { id: 125, date: new Date("2024-12-12"), amount: 599.99, status: "processing" }
  ];
  
  activities = [
    { timestamp: new Date("2024-12-12T14:30:00"), text: "  商品をカートに追加  " },
    { timestamp: new Date("2024-12-12T14:25:00"), text: "商品を閲覧" },
    { timestamp: new Date("2024-12-12T14:20:00"), text: "  ログイン  " }
  ];
}
</script>

この例で使用したフィルター:

  • trim|cap: 空白削除してから先頭大文字化
  • lc: メールアドレスを小文字化
  • date, datetime, time: 日時の表示形式
  • locale: 数値の3桁区切り
  • uc: ランクを大文字表示
  • pad,8,0: 注文番号を8桁に0埋め
  • ymd: 日付をYYYY-MM-DD形式
  • fix,2: 金額を小数点2桁
  • eq: ステータスの比較

チェーンの活用

複数のフィルターを連携させた高度な例:

<!-- 価格を10%割引してから小数点2桁表示 -->
<p>${{ product.price|mul,0.9|fix,2 }}</p>

<!-- テキストをトリムして大文字化し、最初の20文字を取得 -->
<p>{{ description|trim|uc|slice,0,20 }}</p>

<!-- 数値を四捨五入してから文字列化し、0埋め -->
<p>{{ score|round|string|pad,3,0 }}</p>

<!-- 空の場合デフォルト値を使い、大文字化 -->
<p>{{ nickname|defaults,ゲスト|uc }}</p>

まとめ

今日は、パイプフィルターの活用方法を学びました:

フィルターの特徴:

  • 純粋関数(状態なし、副作用なし)
  • 単一値の変換のみ
  • パラメータをカンマで指定
  • パイプでチェーン可能

主なフィルター分類:

  • 比較: eq, ne, not, lt, le, gt, ge
  • 算術: inc, dec, mul, div, mod
  • 数値: fix, locale, percent, round, floor, ceil
  • 文字列: uc, lc, cap, trim, slice, pad, rep, rev
  • 型変換: int, float, boolean, number, string
  • 日付: date, time, datetime, ymd
  • 真偽値: truthy, falsy, defaults

使い分け:

  • 単純な変換 → フィルター
  • 複雑なロジック → getter
  • 複数値の合成 → getter

次回予告:
明日は、「実践:Todoアプリを作る」として、Week 2で学んだすべての機能を使って実用的なアプリケーションを構築します。CRUD操作、フィルタリング、ローカルストレージとの連携を実装します。


次回: Day 13「実践:Todoアプリを作る」

パイプフィルターについて質問があれば、コメントでぜひ!

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?