RxJSで検索バーを作成する方法
著者は、 Write for DOnations プログラムの一環として、 MozillaFoundationを選択して寄付を受け取りました。
序章
リアクティブプログラミングは、非同期データストリームに関連するパラダイムであり、プログラミングモデルはすべてを時間の経過とともに広がるデータストリームと見なします。 これには、キーストローク、HTTPリクエスト、印刷されるファイル、さらには配列の要素も含まれます。これらは、非常に短い間隔でタイミングが取られていると見なすことができます。 非同期データは言語で一般的であるため、これはJavaScriptに完全に適合します。
RxJS は、JavaScriptのリアクティブプログラミングで人気のあるライブラリです。 RxJSの傘であるReactiveXは、 Java 、 Python 、 C ++ 、などの他の多くの言語で拡張されています。 Swift 、およびDart。 RxJSは、AngularやReactなどのライブラリでも広く使用されています。
RxJSの実装は、一定期間のデータを認識して処理できる連鎖関数に基づいています。 これは、引数とコールバックのリストを受け取る関数だけでRxJSのほぼすべての側面を実装し、そうするように指示されたときにそれらを実行できることを意味します。 RxJSを取り巻くコミュニティはこの手間のかかる作業を行っており、その結果、あらゆるアプリケーションで直接使用して、クリーンで保守可能なコードを記述できるAPIが作成されています。
このチュートリアルでは、RxJSを使用して、リアルタイムの結果をユーザーに返す機能豊富な検索バーを作成します。 また、HTMLとCSSを使用して検索バーをフォーマットします。 最終結果は次のようになります。
検索バーのように一般的で一見単純に見えるものには、さまざまなチェックを行う必要があります。 このチュートリアルでは、RxJSが、かなり複雑な一連の要件を、管理しやすく理解しやすいコードに変換する方法を示します。
前提条件
このチュートリアルを開始する前に、次のものが必要です。
- Atom 、 Visual Studio Code 、 SublimeTextなどのJavaScript構文の強調表示をサポートするテキストエディター。 これらのエディターは、Windows、macOS、およびLinuxで使用できます。
- HTMLとJavaScriptを一緒に使用することに精通していること。 詳細については、JavaScriptをHTMLに追加する方法をご覧ください。
- JavaScriptでJSONを操作する方法で詳細を学ぶことができる、JSONデータ形式に精通していること。
チュートリアルの完全なコードは、Githubで入手できます。
ステップ1—検索バーの作成とスタイリング
このステップでは、HTMLとCSSを使用して検索バーを作成し、スタイルを設定します。 このコードは、 Bootstrap のいくつかの一般的な要素を使用して、ページの構造化とスタイル設定のプロセスを高速化し、カスタム要素の追加に集中できるようにします。 Bootstrap は、タイポグラフィ、フォーム、ボタン、ナビゲーション、グリッド、その他のインターフェイスコンポーネントなどの一般的な要素のテンプレートを含むCSSフレームワークです。 アプリケーションはAnimate.cssを使用して、検索バーにアニメーションを追加します。
まず、次の名前のファイルを作成します。 search-bar.html
と nano
またはお気に入りのテキストエディタ:
- nano search-bar.html
次に、アプリケーションの基本構造を作成します。 次のHTMLを新しいファイルに追加します。
<!DOCTYPE html>
<html>
<head>
<title>RxJS Tutorial</title>
<!-- Load CSS -->
<!-- Load Rubik font -->
<!-- Add Custom inline CSS -->
</head>
<body>
<!-- Content -->
<!-- Page Header and Search Bar -->
<!-- Results -->
<!-- Load External RxJS -->
<!-- Add custom inline JavaScript -->
<script>
</script>
</body>
</html>
Bootstrapライブラリ全体からCSSが必要なため、先に進んでBootstrapとAnimate.cssのCSSをロードします。
次のコードを下に追加します Load CSS
コメント:
...
<!-- Load CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.0/animate.min.css" />
...
このチュートリアルでは、 GoogleFontsライブラリのRubikというカスタムフォントを使用して、検索バーのスタイルを設定します。 強調表示されたコードを下に追加してフォントをロードします Load Rubik font
コメント:
...
<!-- Load Rubik font -->
<link href="https://fonts.googleapis.com/css?family=Rubik" rel="stylesheet">
...
次に、カスタムCSSをページの下に追加します Add Custom inline CSS
コメント。 これにより、ページの見出し、検索バー、および結果が読みやすく、使いやすくなります。
...
<!-- Add Custom inline CSS -->
<style>
body {
background-color: #f5f5f5;
font-family: "Rubik", sans-serif;
}
.search-container {
margin-top: 50px;
}
.search-container .search-heading {
display: block;
margin-bottom: 50px;
}
.search-container input,
.search-container input:focus {
padding: 16px 16px 16px;
border: none;
background: rgb(255, 255, 255);
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1) !important;
}
.results-container {
margin-top: 50px;
}
.results-container .list-group .list-group-item {
background-color: transparent;
border-top: none !important;
border-bottom: 1px solid rgba(236, 229, 229, 0.64);
}
.float-bottom-right {
position: fixed;
bottom: 20px;
left: 20px;
font-size: 20px;
font-weight: 700;
z-index: 1000;
}
.float-bottom-right .info-container .card {
display: none;
}
.float-bottom-right .info-container:hover .card,
.float-bottom-right .info-container .card:hover {
display: block;
}
</style>
...
すべてのスタイルが配置されたので、ヘッダーと入力バーを定義するHTMLを Page Header and Search Bar
コメント:
...
<!-- Content -->
<!-- Page Header and Search Bar -->
<div class="container search-container">
<div class="row justify-content-center">
<div class="col-md-auto">
<div class="search-heading">
<h2>Search for Materials Published by Author Name</h2>
<p class="text-right">powered by <a href="https://www.crossref.org/">Crossref</a></p>
</div>
</div>
</div>
<div class="row justify-content-center">
<div class="col-sm-8">
<div class="input-group input-group-md">
<input id="search-input" type="text" class="form-control" placeholder="eg. Richard" aria-label="eg. Richard" autofocus>
</div>
</div>
</div>
</div>
...
これは、Bootstrapのグリッドシステムを使用して、ページヘッダーと検索バーを構成します。 割り当てました search-input
検索バーの識別子。チュートリアルの後半でリスナーにバインドするために使用します。
次に、検索結果を表示する場所を作成します。 下 Results
コメント、作成 div
とともに response-list
チュートリアルの後半で結果を追加するための識別子:
...
<!-- Results -->
<div class="container results-container">
<div class="row justify-content-center">
<div class="col-sm-8">
<ul id="response-list" class="list-group list-group-flush"></ul>
</div>
</div>
</div>
...
この時点で、 search-bar.html
ファイルは次のようになります。
<!DOCTYPE html>
<html>
<head>
<title>RxJS Tutorial</title>
<!-- Load CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.0/animate.min.css" />
<!-- Load Rubik font -->
<link href="https://fonts.googleapis.com/css?family=Rubik" rel="stylesheet">
<!-- Add Custom inline CSS -->
<style>
body {
background-color: #f5f5f5;
font-family: "Rubik", sans-serif;
}
.search-container {
margin-top: 50px;
}
.search-container .search-heading {
display: block;
margin-bottom: 50px;
}
.search-container input,
.search-container input:focus {
padding: 16px 16px 16px;
border: none;
background: rgb(255, 255, 255);
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1) !important;
}
.results-container {
margin-top: 50px;
}
.results-container .list-group .list-group-item {
background-color: transparent;
border-top: none !important;
border-bottom: 1px solid rgba(236, 229, 229, 0.64);
}
.float-bottom-right {
position: fixed;
bottom: 20px;
left: 20px;
font-size: 20px;
font-weight: 700;
z-index: 1000;
}
.float-bottom-right .info-container .card {
display: none;
}
.float-bottom-right .info-container:hover .card,
.float-bottom-right .info-container .card:hover {
display: block;
}
</style>
</head>
<body>
<!-- Content -->
<!-- Page Header and Search Bar -->
<div class="container search-container">
<div class="row justify-content-center">
<div class="col-md-auto">
<div class="search-heading">
<h2>Search for Materials Published by Author Name</h2>
<p class="text-right">powered by <a href="https://www.crossref.org/">Crossref</a></p>
</div>
</div>
</div>
<div class="row justify-content-center">
<div class="col-sm-8">
<div class="input-group input-group-md">
<input id="search-input" type="text" class="form-control" placeholder="eg. Richard" aria-label="eg. Richard" autofocus>
</div>
</div>
</div>
</div>
<!-- Results -->
<div class="container results-container">
<div class="row justify-content-center">
<div class="col-sm-8">
<ul id="response-list" class="list-group list-group-flush"></ul>
</div>
</div>
</div>
<!-- Load RxJS -->
<!-- Add custom inline JavaScript -->
<script>
</script>
</body>
</html>
このステップでは、HTMLとCSSを使用して検索バーの基本構造をレイアウトしました。 次のステップでは、検索語を受け入れて結果を返すJavaScript関数を記述します。
ステップ2—JavaScriptを書く
検索バーのフォーマットが完了したので、このチュートリアルの後半で作成するRxJSコードの基盤として機能するJavaScriptコードを作成する準備が整いました。 このコードはRxJSと連携して、検索語を受け入れ、結果を返します。
このチュートリアルではBootstrapとJavaScriptが提供する機能は必要ないため、それらをロードすることはありません。 ただし、RxJSを使用します。 以下を追加して、RxJSライブラリをロードします。 Load RxJS
コメント:
...
<!-- Load RxJS -->
<script src="https://unpkg.com/@reactivex/[email protected]/dist/global/Rx.js"></script>
...
ここで、の参照を保存します div
結果が追加されるHTMLから。 強調表示されたJavaScriptコードをに追加します <script>
下のタグ Add custom inline JavaScript
コメント:
...
<!-- Add custom inline JavaScript -->
<script>
const output = document.getElementById("response-list");
</script>
...
次に、APIからのJSON応答をページに表示するHTML要素に変換するコードを追加します。 このコードは、最初に検索バーの内容をクリアしてから、検索結果のアニメーションの遅延を設定します。
強調表示された関数を <script>
タグ:
...
<!-- Add custom inline JavaScript -->
<script>
const output = document.getElementById("response-list");
function showResults(resp) {
var items = resp['message']['items']
output.innerHTML = "";
animationDelay = 0;
if (items.length == 0) {
output.innerHTML = "Could not find any :(";
} else {
items.forEach(item => {
resultItem = `
<div class="list-group-item animated fadeInUp" style="animation-delay: ${animationDelay}s;">
<div class="d-flex w-100 justify-content-between">
<^> <h5 class="mb-1">${(item['title'] && item['title'][0]) || "<Title not available>"}</h5>
</div>
<p class="mb-1">${(item['container-title'] && item['container-title'][0]) || ""}</p>
<small class="text-muted"><a href="${item['URL']}" target="_blank">${item['URL']}</a></small>
<div>
<p class="badge badge-primary badge-pill">${item['publisher'] || ''}</p>
<p class="badge badge-primary badge-pill">${item['type'] || ''}</p>
</div>
</div>
`;
output.insertAdjacentHTML("beforeend", resultItem);
animationDelay += 0.1;
});
}
}
</script>
...
で始まるコードブロック if
検索結果をチェックし、結果が見つからなかった場合はメッセージを表示する条件付きループです。 結果が見つかった場合は、 forEach
ループは、アニメーション付きの結果をユーザーに提供します。
このステップでは、結果を受け入れてページに返すことができる関数を記述して、RxJSのベースをレイアウトしました。 次のステップでは、検索バーを機能させます。
ステップ3—リスナーを設定する
RxJSは、データストリームに関係しています。このプロジェクトでは、ユーザーが入力要素または検索バーに入力する一連の文字です。 このステップでは、入力要素にリスナーを追加して、更新をリッスンします。
まず、 search-input
チュートリアルの前半で追加した識別子:
...
<input id="search-input" type="text" class="form-control" placeholder="eg. Richard" aria-label="eg. Richard" autofocus>
...
次に、の参照を保持する変数を作成します search-input
エレメント。 これは、コードが入力イベントをリッスンするために使用するObservableになります。 Observables
将来の価値観やイベントのコレクションです Observer
リッスンし、コールバック関数とも呼ばれます。
強調表示された行をに追加します <script>
前の手順のJavaScriptでタグを付けます。
...
output.insertAdjacentHTML("beforeend", resultItem);
animationDelay += 0.1;
});
}
}
let searchInput = document.getElementById("search-input");
...
参照入力に変数を追加したので、次を使用します fromEvent
イベントをリッスンするオペレーター。 これにより、特定の種類のイベントの要素であるDOMまたはD ocument O bject Modelにリスナーが追加されます。 DOM要素は html
, body
, div
、 また img
ページ上の要素。 この場合、DOM要素は検索バーです。
次の強調表示された行をあなたの下に追加します searchInput
パラメータを渡す変数 fromEvent
. 君の searchInput
DOM要素は最初のパラメーターです。 これに続いて input
2番目のパラメーターとしてのイベント。これは、コードがリッスンするイベントタイプです。
...
let searchInput = document.getElementById("search-input");
Rx.Observable.fromEvent(searchInput, 'input')
...
リスナーが設定されたので、入力要素で更新が行われるたびにコードは通知を受け取ります。 次のステップでは、演算子を使用してそのようなイベントに対してアクションを実行します。
ステップ4—演算子を追加する
Operators
データに対して操作を実行するという1つのタスクを持つ純粋関数です。 このステップでは、演算子を使用して、バッファリングなどのさまざまなタスクを実行します。 input
パラメータ、HTTPリクエストの作成、結果のフィルタリング。
まず、ユーザーがクエリを入力すると、結果がリアルタイムで更新されることを確認します。 これを実現するには、前の手順のDOM入力イベントを使用します。 DOM入力イベントにはさまざまな詳細が含まれていますが、このチュートリアルでは、ターゲット要素に入力された値に関心があります。 次のコードを追加して、 pluck
オブジェクトを取得し、指定されたキーの値を返す演算子:
...
let searchInput = document.getElementById("search-input");
Rx.Observable.fromEvent(searchInput, 'input')
.pluck('target', 'value')
...
イベントが必要な形式になっているので、検索語の最小値を3文字に設定します。 多くの場合、3文字未満では関連する結果が得られないか、ユーザーがまだ入力中である可能性があります。
を使用します filter
最小値を設定する演算子。 指定された条件を満たす場合、データはさらに下流に渡されます。 長さ条件をより大きいに設定します 2
少なくとも3文字が必要です。
...
let searchInput = document.getElementById("search-input");
Rx.Observable.fromEvent(searchInput, 'input')
.pluck('target', 'value')
.filter(searchTerm => searchTerm.length > 2)
...
また、APIサーバーの負荷を軽減するために、リクエストが500ミリ秒間隔でのみ送信されるようにします。 これを行うには、 debounceTime
演算子は、ストリームを通過する各イベント間の最小指定間隔を維持します。 強調表示されたコードを下に追加します filter
オペレーター:
...
let searchInput = document.getElementById("search-input");
Rx.Observable.fromEvent(searchInput, 'input')
.pluck('target', 'value')
.filter(searchTerm => searchTerm.length > 2)
.debounceTime(500)
...
最後のAPI呼び出し以降に変更がない場合、アプリケーションは検索語も無視する必要があります。 これにより、送信されるAPI呼び出しの数がさらに減り、アプリケーションが最適化されます。
例として、ユーザーは次のように入力できます super cars
、最後の文字を削除します(用語を作成します super car
)、削除した文字を追加して、用語を元に戻します。 super cars
. その結果、用語は変更されなかったため、検索結果は変更されません。 このような場合、操作を実行しないことは理にかなっています。
を使用します distinctUntilChanged
これを構成するオペレーター。 この演算子は、ストリームを介して渡された以前のデータを記憶し、異なる場合にのみ別のデータを渡します。
...
let searchInput = document.getElementById("search-input");
Rx.Observable.fromEvent(searchInput, 'input')
.pluck('target', 'value')
.filter(searchTerm => searchTerm.length > 2)
.debounceTime(500)
.distinctUntilChanged()
...
ユーザーからの入力を調整したので、検索語を使用してAPIにクエリを実行するコードを追加します。 これを行うには、AJAXのRxJS実装を使用します。 AJAXは、読み込まれたページのバックグラウンドでAPI呼び出しを非同期的に行います。 AJAXを使用すると、新しい検索用語の結果でページを再読み込みすることを回避でき、サーバーからデータをフェッチすることでページの結果を更新することもできます。
次に、使用するコードを追加します switchMap
AJAXをアプリケーションにチェーンします。 また、使用します map
入力を出力にマップします。 このコードは、渡された関数を、によって発行されたすべてのアイテムに適用します。 Observable
.
...
let searchInput = document.getElementById("search-input");
Rx.Observable.fromEvent(searchInput, 'input')
.pluck('target', 'value')
.filter(searchTerm => searchTerm.length > 2)
.debounceTime(500)
.distinctUntilChanged()
.switchMap(searchKey => Rx.Observable.ajax(`https://api.crossref.org/works?rows=50&query.author=${searchKey}`)
.map(resp => ({
"status" : resp["status"] == 200,
"details" : resp["status"] == 200 ? resp["response"] : [],
"result_hash": Date.now()
})
)
)
...
このコードは、API応答を次の3つの部分に分割します。
status
:APIサーバーから返されるHTTPステータスコード。 このコードは受け入れるだけです200
、または成功した応答。details
:受信した実際の応答データ。 これには、クエリされた検索語の結果が含まれます。result_hash
:APIサーバーによって返される応答のハッシュ値。このチュートリアルの目的ではUNIXタイムスタンプです。 これは、結果が変更されたときに変更される結果のハッシュです。 一意のハッシュ値により、アプリケーションは結果が変更されており、更新する必要があるかどうかを判断できます。
システムに障害が発生したため、エラーを処理できるようにコードを準備する必要があります。 API呼び出しで発生する可能性のあるエラーを処理するには、 filter
成功した応答のみを受け入れる演算子:
...
let searchInput = document.getElementById("search-input");
Rx.Observable.fromEvent(searchInput, 'input')
.pluck('target', 'value')
.filter(searchTerm => searchTerm.length > 2)
.debounceTime(500)
.distinctUntilChanged()
.switchMap(searchKey => Rx.Observable.ajax(`https://api.crossref.org/works?rows=50&query.author=${searchKey}`)
.map(resp => ({
"status" : resp["status"] == 200,
"details" : resp["status"] == 200 ? resp["response"] : [],
"result_hash": Date.now()
})
)
)
.filter(resp => resp.status !== false)
...
次に、応答で変更が検出された場合にのみDOMを更新するコードを追加します。 DOMの更新はリソースを大量に消費する操作になる可能性があるため、更新の数を減らすと、アプリケーションにプラスの影響があります。 以来 result_hash
応答が変更された場合にのみ変更されます。この機能を実装するために使用します。
これを行うには、 distinctUntilChanged
以前のような演算子。 コードはこれを使用して、キーが変更された場合にのみユーザー入力を受け入れます。
...
let searchInput = document.getElementById("search-input");
Rx.Observable.fromEvent(searchInput, 'input')
.pluck('target', 'value')
.filter(searchTerm => searchTerm.length > 2)
.debounceTime(500)
.distinctUntilChanged()
.switchMap(searchKey => Rx.Observable.ajax(`https://api.crossref.org/works?rows=50&query.author=${searchKey}`)
.map(resp => ({
"status" : resp["status"] == 200,
"details" : resp["status"] == 200 ? resp["response"] : [],
"result_hash": Date.now()
})
)
)
.filter(resp => resp.status !== false)
.distinctUntilChanged((a, b) => a.result_hash === b.result_hash)
...
以前に使用した distinctUntilChanged
演算子を使用して、データ全体が変更されたかどうかを確認しますが、この場合は、応答で更新されたキーを確認します。 応答全体を比較することは、単一のキーの変更を識別することと比較すると、リソースコストがかかります。 キーハッシュは応答全体を表すため、応答の変更を識別するために自信を持って使用できます。
この関数は、以前に見た値と新しい値の2つのオブジェクトを受け入れます。 これら2つのオブジェクトからハッシュをチェックして、 True
これらの2つの値が一致する場合、データはフィルターで除外され、パイプラインでそれ以上渡されません。
このステップでは、ユーザーが入力した検索語を受け取り、それに対してさまざまなチェックを実行するパイプラインを作成しました。 チェックが完了すると、API呼び出しが行われ、結果をユーザーに表示する形式で応答が返されます。 必要に応じてAPI呼び出しを制限することにより、クライアント側とサーバー側の両方でリソースの使用を最適化しました。 次のステップでは、入力要素のリッスンを開始するようにアプリケーションを構成し、その結果をページにレンダリングする関数に渡します。
ステップ5—サブスクリプションですべてをアクティブ化する
subscribe
は、オブザーバーがによって発行されたデータイベントを確認できるようにするリンクの最後の演算子です。 Observable
. 次の3つのメソッドを実装します。
onNext
:これは、イベントが受信されたときに何をするかを指定します。onError
:これはエラーの処理を担当します。 への呼び出しonNext
とonCompleted
このメソッドが呼び出されると作成されません。onCompleted
:このメソッドは次の場合に呼び出されますonNext
最後に呼ばれました。 パイプラインで渡されるデータはこれ以上ありません。
サブスクライバーのこの署名は、レイジー実行を実現できるようにするものです。これは、 Observable
パイプラインを作成し、サブスクライブした場合にのみ動作させます。 この例をコードで使用することはありませんが、以下にその方法を示します。 Observable
サブスクライブできます:
次に、サブスクライブします Observable
UIでのレンダリングを担当するメソッドにデータをルーティングします。
...
let searchInput = document.getElementById("search-input");
Rx.Observable.fromEvent(searchInput, 'input')
.pluck('target', 'value')
.filter(searchTerm => searchTerm.length > 2)
.debounceTime(500)
.distinctUntilChanged()
.switchMap(searchKey => Rx.Observable.ajax(`https://api.crossref.org/works?rows=50&query.author=${searchKey}`)
.map(resp => ({
"status" : resp["status"] == 200,
"details" : resp["status"] == 200 ? resp["response"] : [],
"result_hash": Date.now()
})
)
)
.filter(resp => resp.status !== false)
.distinctUntilChanged((a, b) => a.result_hash === b.result_hash)
.subscribe(resp => showResults(resp.details));
...
これらの変更を行った後、ファイルを保存して閉じます。
コードの記述が完了したので、検索バーを表示してテストする準備が整いました。 ダブルクリック search-bar.html
ファイルをWebブラウザで開きます。 コードが正しく入力されている場合は、検索バーが表示されます。
検索バーにコンテンツを入力してテストします。
このステップでは、 Observable
コードをアクティブ化します。 これで、定型化され機能する検索バーアプリケーションができました。
結論
このチュートリアルでは、ユーザーにリアルタイムの結果を提供するRxJS、CSS、およびHTMLを使用して機能豊富な検索バーを作成しました。 検索バーには最低3文字が必要で、自動的に更新され、クライアントとAPIサーバーの両方に最適化されています。
複雑な要件のセットと見なすことができるものは、18行のRxJSコードで作成されました。 このコードは読みやすいだけでなく、スタンドアロンのJavaScript実装よりもはるかにクリーンです。 これは、コードが将来的に理解、更新、および保守しやすくなることを意味します。
RxJSの使用の詳細については、公式APIドキュメントをご覧ください。