5
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?

CBcloudAdvent Calendar 2024

Day 7

TypeScriptのRecord型で実現する柔軟なUI: 動的検索フォームと可変列テーブルの実装

Posted at

はじめに

フロントエンド開発において、ユーザーの要求に応じて動的に変化するUIは非常に重要です。特に、検索フォームやデータテーブルのような複雑なコンポーネントでは、柔軟性と型安全性の両立が求められます。TypeScriptのRecord型を活用することで、これらの課題を効果的に解決できます。

この記事では、以下の2つのシナリオに焦点を当て、Record型を使った実装方法を詳しく解説します。

  1. 動的に検索項目を増減できる検索フォーム
  2. 表示する列が可変なデータテーブル

TypeScriptのRecord型とは?

Record型は、オブジェクトのキーと値の型を指定するためのユーティリティ型です。Record<K, T>の形式で使用し、KがキーのType、Tが値のTypeを表します。これにより、動的なオブジェクト構造を型安全に扱うことができます。

この記事のサンプルコードはVue 3のComposition API(<script setup>構文)を使用しています。

動的検索フォームの実装

基本的な構造

まず、検索項目を表現するための型を定義します:

type FieldType = 'text' | 'number' | 'date'

// フィールドの定義
interface FieldDefinition {
  key: string
  label: string
  type: FieldType
}

// 検索フォームの値
type SearchFormValues = Record<string, string | number | Date>

// フィールド定義(通常はAPIから取得)
const fieldDefinitions = ref<FieldDefinition[]>([
  { key: 'name', label: '名前', type: 'text' },
  { key: 'age', label: '年齢', type: 'number' },
  { key: 'birthdate', label: '生年月日', type: 'date' },
  { key: 'email', label: 'メールアドレス', type: 'text' },
])

この構造により、検索フィールドを動的に追加・削除できるようになります。

検索フォームコンポーネント(Vue 3 Composition API)

<script lang="ts" setup>

const searchValues = ref<SearchFormValues>({
  name: '',
})

// 選択中のフィールドキー
const activeFields = computed(() => Object.keys(searchValues.value))

// 追加可能なフィールド
const availableFields = computed(() =>
  fieldDefinitions.value.filter(
    (field) => !activeFields.value.includes(field.key),
  ),
)

// フィールド定義を取得するヘルパー関数
const getFieldDefinition = (key: string) =>
  fieldDefinitions.value.find((field) => field.key === key)

const addField = (event: Event) => {
  const target = event.target as HTMLSelectElement
  const selectedKey = target.value
  const field = getFieldDefinition(selectedKey)
  if (field) {
    searchValues.value[selectedKey] = field.type === 'number' ? 0 : ''
  }
}

const removeField = (key: string) => {
  const { [key]: _, ...rest } = searchValues.value
  searchValues.value = rest
}

const handleSubmit = () => {
  console.log('検索条件:', searchValues.value)
  // ここで検索ロジックを実装
}
</script>

<template>
  <div>
    <form @submit.prevent="handleSubmit">
      <div v-for="key in activeFields" :key="key">
        <label :for="key">{{ getFieldDefinition(key)?.label }}</label>
        <input
          :id="key"
          v-model="searchValues[key]"
          :type="getFieldDefinition(key)?.type"
        />
        <button type="button" @click="removeField(key)">削除</button>
      </div>

      <div v-if="availableFields.length">
        <label for="field-select">フィールド追加:</label>
        <select id="field-select" @change="addField">
          <option value="">選択してください</option>
          <option
            v-for="field in availableFields"
            :key="field.key"
            :value="field.key"
          >
            {{ field.label }}
          </option>
        </select>
      </div>

      <button type="submit">検索</button>
    </form>
  </div>
</template>


このコンポーネントでは、Record型を使用して動的なフィールドを管理しています。ユーザーは自由にフィールドを追加・削除でき、各フィールドの型も適切に管理されます。

可変列テーブルの実装

テーブルの型定義

interface ColumnDefinition {
  key: string
  label: string
}

// プロジェクトの行データ型
type ProjectData = {
  id: number // idは固定
} & Record<string, string | number | Date>

// これを追加削除したら列が可変になる
const columns: ColumnDefinition[] = [
  { key: 'projectName', label: 'プロジェクト名' },
  { key: 'deadline', label: '期日' },
  { key: 'assignee', label: '担当者' },
  { key: 'status', label: 'ステータス' },
  { key: 'estimatedHours', label: '予定工数' },
  { key: 'progress', label: '進捗率' },
  { key: 'priority', label: '優先度' },
]

この構造により、列の定義と各行のデータを柔軟に管理できます。

テーブルコンポーネント(Vue 3 Composition API)

<script lang="ts" setup>

// 渡って来なかった項目は値が表示されない
const projects: ProjectData[] = [
  {
    id: 1,
    projectName: '新規ECサイト構築',
    deadline: '2024-03-31',
    assignee: '山田太郎',
    status: '進行中',
    estimatedHours: 160,
    progress: 35,
    priority: '',
  },
  {
    id: 2,
    projectName: 'レガシーシステム改修',
    deadline: '2024-06-30',
    assignee: '鈴木花子',
    status: '計画中',
    estimatedHours: 240,
    priority: '',
  },
  {
    id: 3,
    projectName: 'モバイルアプリUI改善',
    assignee: '田中一郎',
    status: 'レビュー中',
    estimatedHours: 80,
    progress: 90,
    priority: '',
  },
]

</script>

<template>
  <table>
    <thead>
      <tr>
        <th v-for="col in columns" :key="col.key">
          {{ col.label }}
        </th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="project in projects" :key="project.id">
        <td v-for="col in columns" :key="col.key">
          {{ project[col.key] }}
        </td>
      </tr>
    </tbody>
  </table>
</template>


このコンポーネントでは、columnsプロパティを使用して動的に列を生成し、dataの各要素をRecord型として扱うことで、柔軟なデータ表示を実現しています。
これにより、DBに項目名を持たせることができ自由度高く項目の追加/削除が実現できる。

まとめ

TypeScriptのRecord型を活用することで、動的な検索フォームや可変列テーブルといった柔軟なUIコンポーネントを型安全に実装できます。これにより、以下のメリットが得られます:

  1. コードの可読性と保守性の向上
  2. 実行時エラーの減少
  3. 開発効率の向上

Record型の特性を理解し、適切に活用することで、より柔軟で堅牢なフロントエンド開発が可能になります。ぜひ、自身のプロジェクトでこれらのテクニックを試してみてください。

参考資料

  1. TypeScript公式ドキュメント - Utility Types
5
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
5
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?