JavaScript(TypeScript)で複合検索を実装しよう

TwitterFacebookHatena

TL;DR

このページでは、JavaScript(TypeScript) を使った複合検索の実装方法について解説しますね。複合検索は、複数の条件を組み合わせて情報を取得する考え方です。たとえば、オンラインショッピングサイトにて「価格」、「評価」、「ブランド名」の 3 つの条件で商品を検索することができます。これが複合検索の一例です。

開発環境

開発環境 バージョン
Next.js 13.4.4
TypeScript 5.0.4
Emotion 11.11.0
React 18.2.0

複合検索のポイント

複合検索のポイントをまとめると概ね次の 3 点が必要です。

  • filter メソッドを使う
  • includes メソッドを使う
  • 非同期処理(Async/Await)とフェッチ API で、外部 API からデータを取得する

fileter メソッド

JavaScript のfilterメソッドは、配列の中から特定の条件に一致する要素だけを取り出すのに非常に便利です。複合検索のように複数の条件を満たす要素を見つける場合には特に役立ちます。

const numbers = [1, 2, 3, 4, 5, 6]
const evenNumbers = numbers.filter((number) => number % 2 === 0)

console.log(evenNumbers) // [2, 4, 6] が出力されます

includes メソッド

JavaScript のincludesメソッドは配列や文字列に特定の要素や文字列が含まれているかを調べます。要素が見つかった場合はtrue、見つからなかった場合はfalseを返します。これにより、検索キーワードがデータ内に存在するかどうかを手早く確認することができます。

const fruits = ['apple', 'banana', 'cherry']
const hasApple = fruits.includes('apple')

console.log(hasApple) // true が出力されます

非同期処理(Async/Await)とフェッチ API

フェッチ API は、JavaScript でネットワークリクエスト(主に HTTP リクエスト)を行うためのものです。非同期処理で、Promise を返します。これを利用することで、データを取得したり、API にデータを送信したりすることが可能です。

fetch('https://jsonplaceholder.typicode.com/posts/1')
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => console.error('エラー:', error))

JavaScript で複合検索を実装する

まずは、簡単な HTML と JavaScript で実装してみましょう。

テキストフィールド、ラジオボタン、セレクトボタンを配置します。

<label for="name">名前:</label>
<input type="text" id="name" />

<label for="category">カテゴリー:</label>
<input type="radio" id="fruit" name="category" value="Fruit" />
<label for="fruit">フルーツ</label>
<input type="radio" id="vegetable" name="category" value="Vegetable" />
<label for="vegetable">野菜</label>

<button onclick="search()">検索</button>

<ul id="results"></ul>

そして JavaScript のコードです。HTML の検索ボタンがクリックされた時に、入力された名前と選択されたカテゴリーを取得し、それらを条件として商品リストを検索します。検索結果は HTML のリストに追加されます。

const products = [
  { id: 1, name: 'Apple', category: 'Fruit' },
  { id: 2, name: 'Banana', category: 'Fruit' },
  { id: 3, name: 'Orange', category: 'Fruit' },
  { id: 4, name: 'Tomato', category: 'Vegetable' },
  { id: 5, name: 'Carrot', category: 'Vegetable' },
]

function search() {
  // 入力された名前と選択されたカテゴリーを取得
  const name = document.getElementById('name').value
  const category = document.querySelector('input[name="category"]:checked').value

  // 商品リストを検索
  const results = products.filter((product) => (!name || product.name.includes(name)) && (!category || product.category === category))

  // 検索結果を表示
  const resultsElement = document.getElementById('results')
  resultsElement.innerHTML = ''
  for (const product of results) {
    const listItem = document.createElement('li')
    listItem.textContent = `${product.name} (${product.category})`
    resultsElement.appendChild(listItem)
  }
}

以上で、JavaScript と HTML を使った複合検索のサンプルが完成です。なお、上記コードはサンプルであり、実際の実装では入力値の検証やエラーハンドリングなどの処理が必要となります。

TypeScript で複合検索を実装する

JavaScript 部分を TypeScript に書き換えたものは以下のようになります。

// "Product"という名前の型を作ります。これは商品の情報を表すものです。
type Product = {
  id: number // 商品の番号
  name: string // 商品の名前
  category: string // 商品のカテゴリー(種類)
}

// 商品のリストを作ります。
const products: Product[] = [
  { id: 1, name: 'Apple', category: 'Fruit' },
  { id: 2, name: 'Banana', category: 'Fruit' },
  { id: 3, name: 'Orange', category: 'Fruit' },
  { id: 4, name: 'Tomato', category: 'Vegetable' },
  { id: 5, name: 'Carrot', category: 'Vegetable' },
]

// 検索の機能を作ります。
function search(): void {
  // 入力した名前と選択したカテゴリーを取得します。
  const name = (document.getElementById('name') as HTMLInputElement).value
  const category = (document.querySelector('input[name="category"]:checked') as HTMLInputElement).value

  // 入力と選択に合う商品をリストから探します。
  const results = products.filter((product) => (!name || product.name.includes(name)) && (!category || product.category === category))

  // 結果を表示する場所を探します。
  const resultsElement = document.getElementById('results')
  if (resultsElement) {
    // 前回の結果を消します。
    resultsElement.innerHTML = ''
    // 新しい結果を表示します。
    for (const product of results) {
      const listItem = document.createElement('li')
      listItem.textContent = `${product.name} (${product.category})`
      if (resultsElement) {
        resultsElement.appendChild(listItem)
      }
    }
  }
}

ここでの重要な TypeScript の特性は、型アノテーションと型アサーションです。Product型を定義してproducts配列に適用しています。また、DOM メソッドから返される値が具体的にどの型かを TypeScript に教えている部分が型アサーションとなります。これにより、TypeScript はそれらの値が特定のメソッドを持つことを認識し、その利用を可能にします。

また、関数searchの戻り値の型をvoidと明示しています。これにより、この関数が値を返さないことを TypeScript に伝えています。

API から複合検索の結果を取得する

API を使う場合、最初にすべてのデータを API からフェッチして、そのデータに対してフィルタリングを行う方法があります。以下にその実装例を示します。

<label>
  検索語:
  <input type="text" id="searchTerm" />
</label>

<label>
  ユーザID:
  <input type="number" id="userId" />
</label>

<button id="searchButton">検索</button>

<div id="results"></div>

JSON ファイルは、jsonplaceholderを使います。

// APIから取得する投稿の型定義
type Post = {
  userId: number
  id: number
  title: string
  body: string
}

// APIから投稿を取得する関数
// Promise<Post[]>は、Post型の配列を返すPromiseを返すという意味
async function fetchPosts(): Promise<Post[]> {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts')
  const posts: Post[] = await response.json()
  return posts
}

// フィルタリングを行う関数
function filterPosts(posts: Post[], searchTerm: string, userId: number) {
  return posts.filter(
    (post) =>
      post.title.includes(searchTerm) && // タイトルに検索語が含まれている
      post.userId === userId // ユーザIDが一致する
  )
}

// 検索ボタンが押されたときの処理
document.getElementById('searchButton')?.addEventListener('click', async () => {
  // ユーザの入力を取得
  const searchTerm = (document.getElementById('searchTerm') as HTMLInputElement)?.value
  const userId = Number((document.getElementById('userId') as HTMLInputElement)?.value)

  // APIからデータを取得
  const posts = await fetchPosts()

  // フィルタリングを行う
  const filteredPosts = filterPosts(posts, searchTerm, userId)

  // 結果を表示
  const resultsDiv = document.getElementById('results')
  resultsDiv?.innerHTML = ''
  for (const post of filteredPosts) {
    const postDiv = document.createElement('div')
    postDiv.textContent = `タイトル: ${post.title}, ユーザID: ${post.userId}`
    resultsDiv?.appendChild(postDiv)
  }
})

この例では、まず API からすべての投稿データを取得しています。次に、それらの投稿からタイトルに特定の検索語が含まれていて、かつユーザ ID が特定の値と一致する投稿を探し出しています。このようにして複合検索を行うことができます。

ただし、データの量が多い場合は、この方法は非効率的である可能性があります。その場合、検索条件をサーバーサイドで適用して結果だけをクライアントに送り返す、いわゆる"サーバーサイド検索"を検討するとよいでしょう。ただし、そのためにはサーバーサイドで検索機能をサポートしている API が必要となります。

JavaScript(TypeScript)で複合検索を実装しよう