はじめに
フロントエンド開発において、ユーザーの要求に応じて動的に変化するUIは非常に重要です。特に、検索フォームやデータテーブルのような複雑なコンポーネントでは、柔軟性と型安全性の両立が求められます。TypeScriptのRecord型を活用することで、これらの課題を効果的に解決できます。
この記事では、以下の2つのシナリオに焦点を当て、Record型を使った実装方法を詳しく解説します。
- 動的に検索項目を増減できる検索フォーム
- 表示する列が可変なデータテーブル
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コンポーネントを型安全に実装できます。これにより、以下のメリットが得られます:
- コードの可読性と保守性の向上
- 実行時エラーの減少
- 開発効率の向上
Record型の特性を理解し、適切に活用することで、より柔軟で堅牢なフロントエンド開発が可能になります。ぜひ、自身のプロジェクトでこれらのテクニックを試してみてください。