JavaScriptのデバウンス関数を学び、Wikipedia検索アプリを構築する

概要: このチュートリアルでは、JavaScriptのデバウンス関数について学び、アプリケーションのパフォーマンスを向上させるためにどのように使用するかを学びます。

デバウンス関数を理解するために、デバウンスプログラミング手法を使用してWikipedia検索アプリケーションを構築します。

プロジェクトフォルダ構造を作成する

まず、プロジェクトのファイルを格納するwikipedia-searchという新しいフォルダを作成します。

次に、wikipedia-searchフォルダ内にjscssimgという3つのフォルダを作成します。これらのフォルダには、それぞれJavaScript、CSS、画像ファイルが格納されます。

3つ目に、cssフォルダにstyle.cssを、jsフォルダにapp.jsを作成します。また、以下の画像をダウンロードしてimgフォルダにコピーします。このロゴは、アプリのUIを作成するために使用します。

最後に、ルートフォルダにindex.htmlファイルを作成します。

プロジェクト構造は以下のようになります

HTMLページを構築する

index.htmlファイルを開き、以下のコードを追加します

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Wikipedia Search</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <header>
        <img src="./img/wikipedia-logo.png" alt="wikipedia">
        <h1>Wikipedia Search</h1>
        <input type="text" name="searchTerm" id="searchTerm" placeholder="Enter a search term...">
    </header>
    <main id="searchResult"></main>
    <script src="js/app.js"></script>
</body>
</html>Code language: HTML, XML (xml)

このHTMLファイルでは

  • まず、<head>セクションでstyle.cssファイルにリンクします。
  • 次に、srcapp.jsファイルにリンクする<script>タグを追加し、</body>タグの直前に配置します。
  • 3つ目に、HTMLページの本文に2つのセクションを追加します。最初のセクションは、Wikipediaのロゴ、見出し、検索ボックスを表示するヘッダーです。2番目のセクションには、検索結果を表示する<main>タグが含まれています。

CSSコードをコピーする

style.cssファイルに移動し、そのコードをコピーして、cssフォルダにあるstyle.cssファイルに貼り付けます。 index.htmlファイルを開くと、次のページのようなものが表示されます。

入力イベントを処理する

まず、querySelector()メソッドを使用して、<input>要素と検索結果要素を選択します。

const searchTermElem = document.querySelector('#searchTerm');
const searchResultElem = document.querySelector('#searchResult');Code language: JavaScript (javascript)

次に、focus()メソッドを呼び出して、<input>要素にフォーカスを設定します。

searchTermElem.focus();Code language: JavaScript (javascript)

3つ目に、<input>要素にinputイベントリスナーをアタッチします。

searchTermElem.addEventListener('input', function (event) {
    console.log(event.target.value);
});Code language: JavaScript (javascript)

<input>要素にテキストを入力すると、inputイベントが発生し、コンソールにテキストが表示されます。

たとえば、<input>要素に「debounce」と入力すると

…コンソールに次のテキストが表示されます

Wikipedia APIを使用して検索結果を取得する

Wikipedia APIは非常にシンプルです。APIキーは必要ありません。

検索語でトピックを取得するには、srsearchクエリパラメータを

&srsearch=<searchTerm>Code language: JavaScript (javascript)

次のURLに追加する必要があります

https://en.wikipedia.org/w/api.php?action=query&list=search&prop=info|extracts&inprop=url&utf8=&format=json&origin=*&srlimit=10Code language: JavaScript (javascript)

…そして、HTTP GETリクエストを送信します。

たとえば、次のURLにHTTP GETリクエストを送信することで、「debounce」キーワードに関連するトピックを取得できます

https://en.wikipedia.org/w/api.php?action=query&list=search&prop=info|extracts&inprop=url&utf8=&format=json&origin=*&srlimit=10&srsearch=debounceCode language: JavaScript (javascript)

ちなみに、上記のURLをWebブラウザで開いて、レスポンスを確認できます。

JavaScriptからは、すべての最新のWebブラウザで使用可能なfetch APIを使用して、HTTP GETリクエストを送信できます。

以下は、検索語を受け取り、WikipediaにHTTP GETリクエストを行い、コンソールに検索結果を表示するsearch()関数を作成します

const search = async (searchTerm) => {
    try {
        const url = `https://en.wikipedia.org/w/api.php?action=query&list=search&prop=info|extracts&inprop=url&utf8=&format=json&origin=*&srlimit=10&srsearch=${searchTerm}`;
        const response = await fetch(url);
        const searchResults = await response.json();

        // show the search result in the console
        console.log({
            'term': searchTerm,
            'results': searchResults.query.search
        });

    } catch (error) {
        console.log(error);
    }
}Code language: JavaScript (javascript)

仕組み

まず、エンドポイントにsrsearchクエリパラメータを追加して、API URLを構築します

const url = `https://en.wikipedia.org/w/api.php?action=query&list=search&prop=info|extracts&inprop=url&utf8=&format=json&origin=*&srlimit=10&srsearch=${searchTerm}`;Code language: JavaScript (javascript)

次に、fetch()メソッドを使用して、HTTP GETリクエストを送信します。 fetch()メソッドはpromiseを返すため、awaitキーワードを使用してレスポンスを待つ必要があります。

fetch()関数によって返されるpromiseには多くのメソッドがあり、そのうちの1つがjson()です。 json()メソッドも、JSON形式の結果に解決される別のpromiseを返します。

awaitキーワードのため、search()関数を次のようにasync関数としてマークする必要があります

const search = async (searchTerm) = {
   /// ...
};Code language: JavaScript (javascript)

json()メソッドの返されたオブジェクトには、多くのプロパティがあります。検索結果を取得するには、searchResults.query.searchプロパティにアクセスする必要があります。

search()メソッドをテストするには、次のようにinputイベントリスナーで呼び出します

searchTermElem.addEventListener('input', function (event) {
    search(event.target.value);
});Code language: JavaScript (javascript)

以下は、完全なapp.jsファイルです

const searchTermElem = document.querySelector('#searchTerm');
const searchResultElem = document.querySelector('#searchResult');

searchTermElem.select();

searchTermElem.addEventListener('input', function (event) {
    search(event.target.value);
});

const search = async (searchTerm) => {
    try {
        const url = `https://en.wikipedia.org/w/api.php?action=query&list=search&prop=info|extracts&inprop=url&utf8=&format=json&origin=*&srlimit=10&srsearch=${searchTerm}`;
        const response = await fetch(url);
        const searchResults = await response.json();

        // show the search result in the console
        console.log({
            'term': searchTerm,
            'results': searchResults.query.search
        });

    } catch (error) {
        console.log(error);
    }
}Code language: JavaScript (javascript)

ここで、index.htmlファイルを開き、入力要素に「debounce」キーワードを入力すると、コンソールに次の結果が表示されます

出力は、入力する文字ごとに関数を実行することを示しています。これは、すべてのテキスト入力に対してAPIを呼び出すため、効率的ではありません。

リクエストの数を制限するには、必要な場合にのみAPIリクエストを送信します。つまり、ユーザーが一時停止するか、一定時間(例:0.5秒)入力を停止した後にのみ、APIリクエストを送信します。

そのためには、setTimeout()関数とclearTimeout()関数を使用できます

  • ユーザーが文字を入力すると、setTimeout()関数を使用して、一定時間後にsearch()関数を実行するようにスケジュールします。
  • ユーザーが引き続き入力している場合は、clearTimeout()関数を使用してタイマーをキャンセルします。ユーザーが一時停止するか入力を停止した場合は、タイマーがスケジュールされた検索関数を実行させます。

以下は、search()関数の新しいバージョンです


let timeoutId;

const search = (searchTerm) => {
    // reset the previous timer
    if (timeoutId) {
        clearTimeout(timeoutId);
    }

    // set up a new timer
    timeoutId = setTimeout(async () => {
        try {
            const url = `https://en.wikipedia.org/w/api.php?action=query&list=search&prop=info|extracts&inprop=url&utf8=&format=json&origin=*&srlimit=10&srsearch=${searchTerm}`;
            const response = await fetch(url);
            const searchResults = await response.json();

            // show the search result in the console
            console.log({
                'term': searchTerm,
                'results': searchResults.query.search
            });
        } catch (error) {
            console.log(error);
        }
    }, 500);
};Code language: JavaScript (javascript)

await関連のコードはsetTimeout()のコールバック関数に移動されたため、コールバックにasyncキーワードをマークし、search()関数からasyncキーワードを削除する必要があります。

Webブラウザでindex.htmlファイルを開き、一時停止(0.5秒)せずに「debounce」キーワードを入力して停止すると、アプリケーションはAPIリクエストを1つだけ行うことがわかります。

そして、この手法はデバウンスとして知られています。

デバウンスとは

APIリクエストのように頻繁に発生する時間のかかるタスクがある場合、アプリケーションのパフォーマンスに影響します。

デバウンスとは、関数が呼び出される回数を制限するプログラミング手法です。

再利用可能なデバウンス関数を開発する

debounce()関数は、関数(fn)を受け取り、呼び出し回数を制限し、関数を返す必要があります

const debounce = (fn) => {
   return (arg) => {
      // logic to limit the number of call fn
      fn(arg);
   };
};Code language: JavaScript (javascript)

以下は、clearTimeout()関数とsetTimeout()関数を使用して、fn関数をデバウンスします

const debounce = (fn) => {
    let timeoutId;

    return (arg) => {
        // cancel the previous timer
        if (timeoutId) {
            clearTimeout(timeoutId);
        }
        // setup a new timer
        timeoutId = setTimeout(() => {
            fn(arg);
        }, 500);
    };
};Code language: JavaScript (javascript)

通常、fn関数は複数の引数を受け取ります。引数リストを使用してfn関数を呼び出すには、apply()メソッドを使用します

const debounce = (fn, delay=500) => {
    let timeoutId;

    return (...args) => {
        // cancel the previous timer
        if (timeoutId) {
            clearTimeout(timeoutId);
        }
        // setup a new timer
        timeoutId = setTimeout(() => {
            fn.apply(null, args);
        }, delay);
    };
};Code language: JavaScript (javascript)

仕組み

  • まず、ハードコードされた数値500delay引数に置き換えて、fn関数を実行するまでの待機時間を指定できるようにします。 delayのデフォルト値は500ミリ秒です。
  • 次に、返された関数に...argsを追加します。 ...argは、レストパラメータであり、fn()関数のすべての引数を配列argsに収集できます。
  • 3つ目に、fn.apply(null, args)は、args配列で指定された引数を使用してfn()関数を実行します。

デバウンス関数を使用する

以下は、search()関数からデバウンスロジックを削除し、代わりにdebounce()関数を使用します

const search = debounce(async (searchTerm) => {
    try {
        const url = `https://en.wikipedia.org/w/api.php?action=query&list=search&prop=info|extracts&inprop=url&utf8=&format=json&origin=*&srlimit=10&srsearch=${searchTerm}`;
        const response = await fetch(url);
        const searchResults = await response.json();

        // show the search result in the console
        console.log({
            'term': searchTerm,
            'results': searchResults.query.search
        });
    } catch (error) {
        console.log(error);
    }
});Code language: JavaScript (javascript)

検索結果をHTMLに変換する

出力には、各検索結果のタイトルとスニペットを表示します。それを行う前に、いくつかのユーティリティ関数が必要です

HTMLタグを削除する

API呼び出しの検索結果のtitlesnippetには、HTMLタグが含まれている場合があります。レンダリングする前にすべてのHTMLタグを削除するのが安全です。

次のユーティリティ関数は、文字列からHTMLタグを削除します

const stripHtml = (html) => {
    let div = document.createElement('div');
    div.html = html;
    return div.textContent;
};Code language: JavaScript (javascript)

stripHtml()関数は、HTML文字列を受け取ります。一時的な<div>要素を作成し、そのinnerHTMLにHTML文字列を割り当て、そのtextContentプロパティを返します。

この関数は、WebブラウザのDOM APIに依存しているため、Webブラウザでのみ機能することに注意してください。

検索語を強調表示する

検索語が検索結果で強調表示されている方が直感的です。

このhighlight()関数は、highlightクラスを持つ<span>タグでキーワードの各出現箇所をラップすることにより、str内のkeywordのすべての出現箇所を強調表示します

const highlight = (str, keyword, className = "highlight") => {
    const hl = `<span class="${className}">${keyword}</span>`;
    return str.replace(new RegExp(keyword, 'gi'), hl);
};Code language: JavaScript (javascript)

関数は、正規表現を使用して、keywordのすべての出現箇所を<span>要素に置き換えることに注意してください。

検索結果をHTMLに変換する

次のgenerateSearchResultHTML()関数は、検索結果をHTMLに変換します


const generateHTML= (results, searchTerm) => {
    return results
        .map(result => {
            const title = highlight(stripHtml(result.title), searchTerm);
            const snippet = highlight(stripHtml(result.snippet), searchTerm);

            return `<article>
                <a href="https://en.wikipedia.org/?curid=${result.pageid}">
                    <h2>${title}</h2>
                </a>
                <div class="summary">${snippet}...</div>
            </article>`;
        })
        .join('');
}Code language: JavaScript (javascript)

仕組み

  • まず、map()メソッドを使用して、各検索結果のHTML表現を返し、join()メソッドを使用して、検索結果(HTML形式)を単一のHTML文字列に結合します。
  • 次に、API呼び出しから返されたtitlesnippetのHTMLタグを削除し、検索語を強調表示します。

検索結果を表示する

generateSearchResultHTML()関数を使用するsearch()メソッドを変更し、その結果をsearchResultElemに追加します。また、検索語が空の場合は検索結果をリセットします

const search = debounce(async (searchTerm) => {

    // if the search term is removed, 
    // reset the search result
    if (!searchTerm) {
        // reset the search result
        searchResultElem.innerHTML = '';
        return;
    }

    try {
        // make an API request
        const url = `https://en.wikipedia.org/w/api.php?action=query&list=search&prop=info|extracts&inprop=url&utf8=&format=json&origin=*&srlimit=10&srsearch=${searchTerm}`;
        const response = await fetch(url);
        const searchResults = await response.json();

        // render search result
        const searchResultHtml = generateSearchResultHTML(searchResults.query.search, searchTerm);

        // add the search result to the searchResultElem
        searchResultElem.innerHTML = searchResultHtml;
    } catch (error) {
        console.log(error);
    }
});Code language: JavaScript (javascript)

これで、Webブラウザでindex.htmlを開くと、動作するアプリケーションが表示されます。

まとめ

このチュートリアルでは、次の重要なポイントを学びました

  • fetch() APIを使用してHTTP GETリクエストを行う。
  • async/awaitキーワードを使用して、非同期コードをよりクリーンにする。
  • デバウンスプログラミング手法を理解し、再利用可能なJavaScript debounce()関数を開発する。
このチュートリアルは役に立ちましたか?