对于用户界面位,我们将使用RxJS-DOM库,这是由RxJS制作的同一团队的库,它提供了方便的Operator来处理DOM和浏览器相关的东西,这将使我们的编程更简洁。对于服务器部分,我们将使用两个完善的节点库,并将一些API与Observables包装在一起,以便在我们的应用程序中使用它们。
一开始的代码如下:
这段代码已经有一个潜在的错误:它可以在DOM准备好之前执行,每当我们尝试在代码中使用DOM元素时就会抛出错误。我们想要的是在触发DOMContentLoaded事件之后加载我们的代码,这表示浏览器已经准备好dom了。
接下来,我们将在HTML中添加一个空表,我们将在下一部分填充地震数据:
有了这个,我们准备开始为我们的仪表板编写新代码。
新仪表板的第一个功能是显示地震的实时列表,包括有关其位置,大小和日期的信息。此列表的数据与来自USGS网站的地图相同。我们首先创建一个函数,在给定props对象参数的情况下返回一个row元素:
props参数与我们从USGS站点检索的JSON中的properties属性相同。
为了生成行,我们将再次订阅地震Observable。此订阅会在表格中为每次收到的新地震创建一行。 我们在initialize函数的末尾添加代码:
pluck运算符从每个地震对象中提取属性值,因为它包含makeRow所需的所有信息。 然后我们将每个地震对象映射到makeRow,将其转换为填充的HTML tr元素。 最后,在订阅中,我们将每个发出的行追加到我们的table中。
每当我们收到地震数据时,这应该得到一个数据稠密的表格。
看起来不错,而且很容易!不过,我们可以做一些改进。首先,我们需要探索RxJS中的一个重要概念:冷热Observable。
无论Observers是否订阅它们,“热”Observable都会发出值。另一方面,“冷”Observables从Observer开始订阅就发出整个值序列。
订阅热Observable的Observer将接收从订阅它的确切时刻发出的值。在那一刻订阅的每个其他Observer将收到完全相同的值。 这类似于JavaScript事件的工作方式。
鼠标事件和股票交易代码是热的Observables的例子。在这两种情况下,Observable都会发出值,无论它是否有订阅者,并且在任何订阅者收听之前可能已经生成了值。这是一个例子:
在该示例中,两个订阅者在发出Observable时都会收到相同的值。 对于JavaScript程序员来说,这种行为感觉很自然,因为它类似于JavaScript事件的工作方式。
现在让我们看看冷Observables是如何工作的。
只有当Observers订阅它时,冷Observable才会发出值。
输出
这似乎没什么问题。 但现在想象我们需要第二个用户在第一个用户加入后三秒钟加入:
输出
如果我们有几个Observers订阅冷的Observable,他们将收到相同序列值的副本。严格来说,尽管观察者共享相同的Observable,但它们并没有共享相同的值序列。如果我们希望Observers共享相同的序列,我们需要一个热的Observable。
我们可以使用publish将冷的Observable变成热的。调用publish会创建一个新的Observable,它充当原始Observable的代理。它通过订阅原始版本并将其收到的值推送给订阅者来实现。
已发布的Observable实际上是一个ConnectableObservable,它有一个名为connect的额外方法,我们调用它来开始接收值。 这允许我们在开始运行之前订阅它:
让我们回到我们的地震示例。到目前为止,我们的代码看起来很合理;我们有一个带有两个订阅的Observable地震:一个在地图上绘制地震,另一个在表格中列出地震。
发生这种情况是因为quakes是一个冷Observable,并且它会将所有值重新发送给每个新订阅者,因此新订阅意味着新的JSONP请求。这会通过网络请求两次相同的资源来影响我们的应用程序性能。
对于下一个示例,我们将使用`share·运算符,当Observers的数量从0变为1时,它自动创建对Observable的预订。 这使我们免于重新连接:
现在地震的行为就像一个热的Observable,我们不必担心我们连接多少观察者,因为他们都会收到完全相同的数据。
我们之前的代码运行良好,但请注意,每次我们收到有关地震的信息时都会插入一个tr节点。 这是低效的,因为每次插入我们都会修改DOM并导致重新绘制页面,使浏览器不必要地计算新布局。 这可能会导致性能下降。
理想情况下,我们会批处理几个传入的地震对象,并每隔几秒插入一批地震对象。手动实现会很棘手,因为我们必须保留计数器和元素缓冲区,我们必须记住每次批量重置它们。 但是使用RxJS,我们可以使用一个基于缓冲区的RxJS运算符,比如bufferWithTime。
这是新代码中正在发生的事情:
使用缓冲区和片段,我们设法保持行插入性能,同时保持应用程序的实时性(最大延迟为半秒)。 现在我们已准备好为我们的仪表板添加下一个功能:交互性!
我们现在在地图上和列表中发生地震,但两个表示之间没有相互作用。例如,每当我们点击列表上的地图时,就可以在地图上居中地震,并在我们将鼠标移动到其行上时突出显示地图上带圆圈的地震。 我们开始吧。
在Leaflet中,您可以在地图上绘制并将绘图放在各自的图层中,以便您可以单独操作它们。 让我们创建一组名为quakeLayer的图层,我们将存储所有地震圈。每个圆圈都是该组内的一个图层。 我们还将创建一个对象codeLayers,我们将存储地震代码和内部图层ID之间的相关性,以便我们可以通过地震ID来查找圆圈:
现在,在初始化内部的地震Observable订阅中,我们将每个圆圈添加到图层组并将其ID存储在codeLayers中。 如果这看起来有点错综复杂,那是因为这是Leaflet允许我们在地图中引用图层的唯一方式。
我们现在创建悬停效果。我们将编写一个新函数isHovering,它返回一个Observable,它发出一个布尔值,表示在任何给定时刻鼠标是否在特定地震圈上:
使用isHovering,我们可以修改创建rows的订阅,这样我们就可以在创建时订阅每行中的事件:
经验丰富的前端开发人员知道在页面上创建许多事件是导致性能不佳的一个因素。 在前面的示例中,我们为每一行创建了三个事件。 如果我们在列表中获得100次地震,我们将在页面周围浮动300个事件,只是为了做一些亮点突出工作! 这对于表现来说太糟糕了,我们可以做得更好。
因为DOM中的事件总是冒泡(从子元素到父元素),前端开发人员中一个众所周知的技术是避免将鼠标事件单独附加到多个元素,而是将它们附加到父元素。 一旦在父项上触发了事件,我们就可以使用事件的target属性来查找作为事件目标的子元素。
因为我们需要为事件click和mouseover提供类似的功能,所以我们将创建一个函数getRowFromEvent:
getRowFromEvent为我们提供了事件发生的表行。 以下是详细信息:
在上一节中,我们在每行上附加事件mouseover和mouseout,以便在每次鼠标输入或退出行时更改地震圈颜色。 现在,我们将仅使用桌面上的mouseover事件,并结合方便的pairwise运算符:
pairwise将每个发射值与先前在阵列中发射的值进行分组。 因为我们总是获得不同的行,所以成对将始终产生鼠标刚刚离开的行和鼠标现在悬停的行。 有了这些信息,就可以相应地为每个地震圈着色。
处理click事件更简单:
我们可以回到订阅quakes来生成行:
我们的代码现在更加干净,并且它不依赖于别处的row。 如果没有row,getRowFromEvent将不会尝试产生任何item。
更重要的是,我们的代码现在非常高效。 无论我们检索的地震信息量如何,我们总是只有一个鼠标悬停事件和单击事件,而不是数百个事件。
首先,让我们为我们的应用程序创建一个文件夹,并安装我们将使用的模块。 (请注意,npm命令的输出可能会因软件包的当前版本而异。)
要使用Twitter API,您需要在Twitter网站中请求使用者密钥和访问令牌。 完成后,使用配置对象创建一个新的Twit对象,如下所示:
现在我们可以创建一个函数onConnect,它将完成搜索推文和将来与客户端通信的所有工作,并且我们可以启动一个WebSocket服务器,一旦WebSocket连接并准备好就会调用onConnect:
我们现在可以启动我们的应用程序,它应该在端口8080上启动WebSocket连接:
由于我们尚未将任何浏览器连接到此服务器,因此尚未打印有关客户端连接的消息。现在让我们切换到dashboard的代码并执行此操作。我们将在RxJS-DOM中使用fromWebSocket运算符:
我们现在可以发送包含我们收到的地震数据的服务器消息:
我们可以为来自服务器的消息设置订阅者:
现在,当我们重新加载浏览器时,客户端消息应出现在服务器终端中:
太棒了! 一旦开始从远程JSONP资源接收地震,浏览器就应该向服务器发送命令。 但是现在,服务器完全忽略了这些消息。 是时候回到我们的推文流代码并用它们做点什么了。
首先,我们将连接到从浏览器客户端到达服务器的消息事件。 每当客户端发送消息时,WebSocket服务器都会发出包含消息内容的消息事件。 在我们的例子中,内容是一个JSON字符串。
我们可以在onConnect函数中编写以下代码:
如果我们重新启动服务器(终端中的Ctrl-C)并重新加载浏览器,我们应该会看到终端上的地震细节打印出来。这是完美的。 现在我们已经准备好开始寻找与我们的地震有关的推文了。
这告诉我们的Twit实例T开始流式传输Twitter状态,按关键字地震过滤。 当然,这是非常通用的,而不是与现在发生的地震直接相关。 但请注意空位置数组。 这是一个纬度和经度边界的数组,我们可以用它们按地理位置过滤推文,以及地震一词。 那更加具体! 好的,让我们订阅这个流并开始向浏览器发送推文:
如果我们重新启动服务器并重新加载浏览器,我们应该在浏览器中收到推文,开发面板中的控制台应该打印推文。
这些推文尚未按地震位置进行过滤。 为此,我们需要对收到的每一条地震信息做以下事情:
这是一种方法:
以下是前面代码中发生的事情的一步一步:
重新启动服务器并重新加载浏览器后,我们应该在浏览器应用程序中收到相关的推文。 但是现在,我们只能看到开发人员控制台中显示的原始对象。 在下一节中,我们将生成HTML以在仪表板中显示推文。
既然我们正在接收来自服务器的推文,那么剩下要做的就是在屏幕上很好地展示它们。 为此,我们将创建一个新的HTML元素,我们附加传入的推文:
我们还将更新socket Observable订阅以处理传入的tweet对象并将它们附加到我们刚刚创建的tweet_container元素:
任何新的推文都会出现在列表的顶部,它们将由makeTweetElement创建,这是一个创建推文元素的简单函数,并使用我们作为参数传递的数据填充它:
此仪表板已经正常运行,但可以进行许多改进。 一些想法,使它更好:
更重要的是,我们已经看到我们可以在客户端和服务器上以相同的方式使用RxJS,在我们的应用程序中随处可见Observable序列抽象。 不仅如此。我们实际上可以在其他编程语言中使用RxJS概念和运算符,因为许多编程语言都支持RxJS。