作者选择Mozilla Foundation作为Write for DOnations计划的一部分进行捐赠。
介绍
反应式编程是一种涉及异步数据流的范例,其中编程模型将所有内容视为随时间推移的数据流。 这包括击键,HTTP请求,要打印的文件,甚至是数组的元素,可以认为这些元素在非常小的时间间隔内定时。 这使得它非常适合JavaScript,因为异步数据在语言中很常见。
RxJS是一个用于JavaScript中的反应式编程的流行库。 ReactiveX是RxJS所依赖的保护伞,它在Java , Python , C ++ , Swift和Dart等许多其他语言中都有扩展。 RxJS也被Angular和React等图书馆广泛使用。
RxJS的实现基于链接的函数,这些函数知道并且能够在一段时间内处理数据。 这意味着可以实现RxJS的几乎每个方面,只需要接收参数和回调列表的函数,然后在发出信号时执行它们。 围绕RxJS的社区已经完成了这个繁重的工作,结果是一个API,您可以直接在任何应用程序中使用它来编写干净和可维护的代码。
在本教程中,您将使用RxJS构建功能丰富的搜索栏,以便向用户返回实时结果。 您还将使用HTML和CSS格式化搜索栏。 最终结果将是这样的:
像搜索栏一样常见且看似简单的东西需要进行各种检查。 本教程将向您展示RxJS如何将一组相当复杂的需求转换为易于理解且易于理解的代码。
先决条件
在开始本教程之前,您需要以下内容:
- 支持JavaScript语法突出显示的文本编辑器,例如Atom , Visual Studio Code或Sublime Text 。 这些编辑器可在Windows,macOS和Linux上使用。
- 熟悉一起使用HTML和JavaScript。 详细了解如何将JavaScript添加到HTML 。
- 熟悉JSON数据格式,您可以在JavaScript中了解如何使用JSON 。
Github上提供了本教程的完整代码。
第1步 – 创建搜索栏并设置样式
在此步骤中,您将使用HTML和CSS创建搜索栏并设置样式。 代码将使用Bootstrap中的一些常用元素来加速页面的结构化和样式化过程,以便您可以专注于添加自定义元素。 Bootstrap是一个CSS框架,包含常用元素的模板,如排版,表单,按钮,导航,网格和其他界面组件。 您的应用程序还将使用Animate.css将动画添加到搜索栏。
您将从使用nano
或您喜欢的文本编辑器创建名为search-bar.html
的文件开始:
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" />...
本教程将使用Google Fonts库中名为Rubik的自定义字体来设置搜索栏的样式。 通过在Load Rubik font
注释下添加突出显示的代码来Load Rubik font
:
...<!-- Load Rubik font --> <link href="https://fonts.googleapis.com/css?family=Rubik" rel="stylesheet">...
接下来,在“ Add Custom inline 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>...
现在您已经拥有了所有样式,添加将在Page Header and Search Bar
注释下定义标题和输入栏的HTML:
...<!-- 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
注释下,使用response-list
标识符创建一个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>...
此时, 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
现在您已经格式化了搜索栏,您已准备好编写JavaScript代码,该代码将作为您将在本教程后面编写的RxJS代码的基础。 此代码将与RxJS一起使用以接受搜索词并返回结果。
由于您不需要本教程中Bootstrap和JavaScript提供的功能,因此您不会加载它们。 但是,您将使用RxJS。 通过在Load RxJS
注释下添加以下内容来加载RxJS库:
...<!-- Load RxJS --> <script src="https://unpkg.com/@reactivex/[email protected]/dist/global/Rx.js"></script>...
现在,您将存储要添加结果的HTML的div
引用。 在Add custom inline JavaScript
注释下的<script>
标记中添加突出显示的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关注数据流,在这个项目中,数据流是用户输入到输入元素或搜索栏的一系列字符。 在此步骤中,您将在input元素上添加一个监听器以监听更新。
首先,记下您在本教程前面添加的search-input
标识符:
...<input id="search-input" type="text" class="form-control" placeholder="eg. Richard" aria-label="eg. Richard" autofocus>...
接下来,创建一个包含search-input
元素引用的变量。 这将成为代码将用于监听输入事件的Observable
。 Observables
是Observer
监听的未来值或事件的集合,也称为回调函数 。
在上一步的JavaScript下的<script>
标记中添加突出显示的行:
... output.insertAdjacentHTML("beforeend", resultItem); animationDelay += 0.1; }); }} let searchInput = document.getElementById("search-input");...
现在您已经为引用输入添加了一个变量,您将使用fromEvent
运算符来监听事件。 这将在DOM或D ocument O bject M odel上添加一个监听器,用于某种事件。 DOM元素可以是页面上的html
, body
, div
或img
元素。 在这种情况下,您的DOM元素是搜索栏。
在searchInput
变量下添加以下突出显示的行,以将参数传递给fromEvent
。 您的searchInput
DOM元素是第一个参数。 接下来是input
事件作为第二个参数,它是代码将监听的事件类型。
... let searchInput = document.getElementById("search-input"); Rx.Observable.fromEvent(searchInput, 'input')...
现在您的监听器已设置,只要输入元素发生任何更新,您的代码就会收到通知。 在下一步中,您将使用运算符对此类事件采取措施。
第4步 – 添加运算符
Operators
是纯函数,只有一个任务 – 对数据执行操作。 在此步骤中,您将使用运算符执行各种任务,例如缓冲input
参数,发出HTTP请求和过滤结果。
您将首先确保在用户输入查询时实时更新结果。 要实现此目的,您将使用上一步中的DOM输入事件。 DOM输入事件包含各种详细信息,但是对于本教程,您感兴趣的是键入目标元素的值。 添加以下代码以使用pluck
运算符获取对象并返回指定键处的值:
... let searchInput = document.getElementById("search-input"); Rx.Observable.fromEvent(searchInput, 'input') .pluck('target', 'value')...
既然事件是必要的格式,您将搜索项最小值设置为三个字符。 在许多情况下,任何少于三个字符的内容都不会产生相关结果,或者用户可能仍处于打字过程中。
您将使用filter
运算符来设置最小值。 如果数据满足指定条件,它将沿着流进一步传递数据。 将长度条件设置为大于2
,至少需要三个字符。
... let searchInput = document.getElementById("search-input"); Rx.Observable.fromEvent(searchInput, 'input') .pluck('target', 'value') .filter(searchTerm => searchTerm.length > 2)...
您还将确保仅以500毫秒的间隔发送请求,以减轻API服务器上的负载。 为此,您将使用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
将输入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响应分为三个部分:
-
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
运算符来查看是否已更改整个数据,但在此实例中,您将检查响应中是否有更新的密钥。 与识别单个密钥中的更改相比,比较整个响应将是资源成本高的。 由于密钥散列代表整个响应,因此可以放心地用于识别响应变化。
该函数接受两个对象,它看到的前一个值和新值。 我们检查这两个对象的哈希值,并在这两个值匹配时返回True
,在这种情况下,数据被过滤掉而不会在管道中进一步传递。
在此步骤中,您创建了一个管道,该管道接收用户输入的搜索词,然后对其执行各种检查。 检查完成后,它会进行API调用并以一种将结果显示给用户的格式返回响应。 您通过在必要时限制API调用来优化客户端和服务器端的资源使用情况。 在下一步中,您将配置应用程序以开始监听input元素,并将结果传递给将在页面上呈现它的函数。
第5步 – 使用订阅激活所有内容
subscribe
是链接的最终运算符,使观察者能够查看Observable
发出的数据事件。 它实现了以下三种方法:
-
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创建了一个功能丰富的搜索栏,为用户提供实时结果。 搜索栏至少需要三个字符,自动更新,并针对客户端和API服务器进行了优化。
可以考虑使用18行RxJS代码创建一组复杂的需求。 该代码不仅对读者友好,而且比独立的JavaScript实现更清晰。 这意味着您的代码将来更容易理解,更新和维护。
要了解有关使用RxJS的更多信息,请查看官方API文档 。