美文网首页
[译]理解响应式编程和RxJS

[译]理解响应式编程和RxJS

作者: flyingjimmy | 来源:发表于2017-10-21 16:31 被阅读0次

RxJS能够让我们很轻松地创建和操控事件和streams,虽然会让开发变得复杂,但是会让异步代码变得易读。

创建大型的异步的应用程序并非易事,其回调函数引发的问题让诸多开发者头疼,我们称其为回调地狱。之前有promises, generators以及async/await来处理回调函数引发的问题。但现在我们有了另外一个解决方案,那就是RxJS

RxJS在其github项目上的定义为“a set of libraries for composing asynchronous and event-based programs using observable sequences and fluent query operators”。说得通俗易懂点就是我们可以从事件以及其他数据源中创建streams,并且我们可以对streams进行合并,销毁,分离等操作,以获得我们想要的数据。

Observable或者stream(数据流)刚开始可能比较难理解。我会把它看成是一段时间内的事件或数据集合,而不是某个时间点上的单一事件或数据。

为了演示它是如何工作的,我们将要创建一个简单的天气应用。这个应用会根据你提交的邮编会返回邮编所在地区的气温。获得返回的气温后,我们会将气温和邮编一同显示在页面上。我们能够在页面上显示多组气温和邮编。最后,我们还会有个定时器能够定时刷新气温。

你可以在Github上查看最终源代码。

更新:这篇文章已经更新到RxJS version 5
对原先的代码只有少量改动,有必要的情况下我会高亮这些改动。

起步

首页我们需要一个HTML页面来加载RxJS,还需要包含一些CSS,代码如下:

<!DOCTYPE html>
  <html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Weather Monitoring in RxJS</title>
    <style>
      #form {
        margin-bottom: 20px;
      }
      .location {
        float: left;
        padding: 10px;
        margin-right: 20px;
        margin-bottom: 20px;
        border: 1px solid #ddd;
        border-radius: 5px;
      }
      .location p {
        margin-top: 10px;
        margin-bottom: 10px;
        text-align: center;
      }
      .zip { font-size: 2em; }
      .temp { font-size: 4em; }
    </style>
  </head>
  <body>
    <div id="app-container">
      <div id="form">
        <label>Zip Code:</label>
        <input type="text" id="zipcode-input">
        <button id="add-location">Add Location</button>
      </div>
    </div>
    <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.all.min.js"></script> -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.0-beta.12/Rx.min.js"></script>
    <script>
      // 我们将在这里编码
      console.log('RxJS included?', !!Rx);
  </script>
  </body>
  </html>

在浏览器中打开这个页面,并打开console面板,如果你能看到RxJS included? true,说明你已经可以开始响应式编程啦。在这个页面上我们有一个表单,表单下面包含邮编的输入框和一个按钮。首先我们的JavaScript将会获取这两个表单元素,然后为它们创建stream。我们还会获取id为app-container的DIV,我们将会在后面用到。

更新:我已经在页面上引用了RxJS 5.0,而不是之前的4.1

// 获取HTML元素
const appContainer = document.getElementById('app-container');
const zipcodeInput = document.getElementById('zipcode-input');
const addLocationBtn = document.getElementById('add-location');

上面是很基本的JavaScript获到DOM节点,没什么特别的

// 成生点击按钮的数据流
const btnClickStream =
  Rx.Observable
    .fromEvent(addLocationBtn, 'click')
    // .map(() => true)
    .mapTo(true)
    .forEach(val => console.log('btnClickStream val', val));

在这里我们用到了RxJS!我们使用了Rx.Observable上的fromEvent方法,它为addLocationBtn的点击事件创建stream。意味着任何时候点击按钮,btnClickStream都会发送事件对象。因为我只需要知道点击事件的发生,所以我将点击事件产生的值用mapTo转换成布尔值true,我认为这样能够简化逻辑。当然这只是我喜欢的方式,如果你不喜欢,可以将它从代码中移除,没问题。最后,为了确保它正常工作,我们使用了forEach,它为数据流增加一个订阅者(subscriber),只是简单的输出值。

更新:RxJS 5.0新增了mapTo,它比map更可读。后者需要用一个函数来返回true。当然两者在这里的作用是一样的。

image

重新加载页面,点击几次按钮,你会发现console面板输出btnClickStream val true,说明代码正常工作了。现在我们去掉forEach,因为不再需要它了。接来下处理邮编,我们需要监听邮编输入框的变化,在这里要做一下过滤,只有输入的长度为5时才去处理,看下面代码:

// 成生邮编输入框的数据流
const zipInputStream =
  Rx.Observable
    .fromEvent(zipcodeInput, 'input')
    .map(e => e.target.value)
    .filter(zip => zip.length === 5)
    .forEach(val => console.log('zipInputStream val', val));

这里我们为邮编输入框创建了stream,使用map从事件中获取输入的值,然后使用filter过滤掉所有长度不为5的输入值。最后我们通过forEach历遍所有的值,将它们输出在console中。

image

同样,我们刷新页面输入值,点击按钮,在console面板查看结果,然后去掉forEach。当调用weather API的时候我们需要让代码可以重用。

// 创建可重用的获取气温的stream
const getTemperature = zip => fetch(`http://api.openweathermap.org/data/2.5/weather?q=${zip},us&units=imperial&APPID=<APPID>`).then(res => res.json());

const zipTemperatureStreamFactory = zip => Rx.Observable.fromPromise(getTemperature(zip)).map(({ main: { temp } }) => { temp, zip });

我们创建了两个函数。第一个getTemperature通过传入zip向weather API请求。因为fetch返回一个promise,所以我们使用了then。这个promise接收到回复,我们再将这个回复发json的形式返回,方便我们更好地处理数据。你需要更换上面代码中的<APPID>,使用你自己的ID,你可以在这里申请APPID。

第二个函数同样使用zip code做参数。我们使用了fromPromise来创建了一个stream。这个操作符是将getTemperature函数返回的promise转换成stream。因为返回的是stream,所以我们可以使用Rx.Observable上的操作符对其进行操作。我们使用map将数据以object的形式返回。注意,在map参数上,我们使用了ES2015新的语法:解构(destructuring)。使用解构能够轻松地将气温数据提取出来。关于解构,你可以访问MDN文档了解更多。

现在我们已经可以从weather API中获取数据了,现在我们将在页面上增加些元素。

// 点击按钮获得邮编,然后请求气温打印在页面上
zipcodeStream
  .flatMap(zipTemperatureStreamFactory)
  .forEach(({ zip, temp }) => {
    const locationEle = document.createElement('div');
    locationEle.id = `zip-${zip}`;
    locationEle.classList.add('location');

    const zipEle = document.createElement('p');
    zipEle.classList.add('zip');
    zipEle.innerText = zip;

    const tempEle = document.createElement('p');
    tempEle.classList.add('temp');
    tempEle.innerHTML = `${temp}°F`;

    locationEle.appendChild(zipEle);
    locationEle.appendChild(tempEle);
    appContainer.appendChild(locationEle);

    zipcodeInput.value = '';
  });

我们在zip code的数据流上使用flatMap操作符。flatMap类似于map,不同的是它返回的是所有Stream中的每个Stream,然后取出每个stream中的值。意味着它会“打平”我们从weather API获得的stream,返回我们需要处理的数据,也就是包含邮编和气温的对象。

接下来就是我们使用forEach来处理我们获得的数据,将它们添加到页面中。最后我们清空输入框的值。

image

重新加载页面,然后在输入框中输入几个邮编,你会看到页面上有新增元素,包含了你输入的邮编以及对应的气温。

现在我们已经能够让气温显示在页面上,但是我们要让它保持更新。所以我们要创建了个stream,让它每个一段时间发射最新的数据。但是在此之前,我们要先拿到所以已经添加到页面上的邮编。怎样拿呢?可以使用ReplaySubject。ReplaySubjuct能够订阅一个stream,并且记住这个stream所有的值。我们就可以在任何时候重新拿到那些值。

// 创建一个stream,以便我们在想要的时候获得邮编
const replayZipsStream = new Rx.ReplaySubject();
zipcodeStream.subscribe(replayZipsStream);

这里我们创建了一个新的ReplaySubject对象,然后在zipcodeStream中订阅。意味着ReplaySubject会记住我们输入的所有的邮编。

// 创建个定时器,更新页面
Rx.Observable
  .interval(20000)
  // .flatMapLatest(() => replayZipsStream)
  .switchMap(() => replayZipsStream)
  .flatMap(zipTemperatureStreamFactory)
  .forEach(({ zip, temp }) => {
    console.log('Updating!', zip, temp);

    const locationEle = document.getElementById(`zip-${zip}`);
    const tempEle = locationEle.querySelector('.temp');

    tempEle.innerHTML = `${temp}°F`;
  });

首先我们要创建个stream,作用是在指定的间隔上发射值。发射什么值并不是我们要关心的,我们只是要在这个stream发射值的时候执行其他操作。然后我们使用了一个新的操作符switchMap。这里使用switchMap而不是flatMap的原因是我们只需要在replayZipsStream上有一个订阅者(subscriber)。如果我们使用flatMap,我们就会在相同的ReplaySubject上有多个订阅者,这会导致我们向weather API发送多个额外的请求。这时候我们就得到了一个包含邮编的stream,就像之前我们在页面中添加邮编时一样。所以我们可以用同样的方式处理stream返回的值。我们使用flatMap,把zipTempateratureStreamFactory传进去,后者会向weather API发送请求。最后,遍历所有返回回来的数据,将它们更新到页面上。

更新:在RxJS 5.0版本中,flatMapLatest已更改成switchMap

image

最后一次载新页面,添加几个邮编,你会看到它们会被添加到页面中。等待20秒,你会在console面板上看到消息,告诉我们所有东西都已经被更新了。你可能不会在页面上看到变化,因为在这20秒内,气温可能并不会发生变化。当然你可以在Rx.Observable.interval上自由改动间隔时间。

使用Auth0 Lock

假设你现在决定使用Auth0 Lock为你的天气应用添加身份验证,那么应该怎么做呢?其实挺简单的,因为Auth0 Lock的库把大部分的工作都做好了,我们所要做的就是一个按钮,点击后会显示一个modal。

首先,我们需要引入Auth0 Lock的库,初始化Lock,增加一个登陆按钮,点击后会弹出一个modal。

<button id="login">Login</button>
  <script src="http://cdn.auth0.com/js/lock/10.x.y/lock.min.js"></script>
  <script>
    // Initiating our Auth0Lock
    var lock = new Auth0Lock(
      'YOUR_CLIENT_ID',
      'YOUR_NAMESPACE'
    );

    // Listening for the authenticated event
    lock.on("authenticated", function(authResult) {
      // Use the token in authResult to getProfile() and save it to localStorage
      lock.getProfile(authResult.idToken, function(error, profile) {
        if (error) {
          // Handle error
          return;
        }

        localStorage.setItem('idToken', authResult.idToken);
        localStorage.setItem('profile', JSON.stringify(profile));
      });
    });
  </script>

剩下唯一要做的事是将点击登陆按钮转换成stream,只要这个stream发射数据,我们就打开modal。

Rx.Observable
 .fromEvent(document.getElementById('login'), 'click')
 .forEach(() => lock.open());

至此,所以工作已经完了。

总结

Observables或者streams刚开始可能并不容易理解。我会把它想象成一段时间内事件的集合而不是单一事件。一旦把这个搞清楚,那么把DOM上面所有事件想象成streams都不是难事。使用RxJS可以很容易地创建streams,并且很容易操作。相比于其实框架或库,它能让你代码逻辑更清晰。

本文翻译自 https://auth0.com/blog/understanding-reactive-programming-and-rxjs/

相关文章

  • [译]理解响应式编程和RxJS

    RxJS能够让我们很轻松地创建和操控事件和streams,虽然会让开发变得复杂,但是会让异步代码变得易读。 创建大...

  • RxJS 用法(操作符)总结

    RxJS 用法总结 本博客只总结了常用的部分RxJS方法, RxJS可能会让你对响应式编程产生新的理解。RxJS ...

  • rxjs

    RxJS 用法总结 本博客只总结了常用的部分RxJS方法, RxJS可能会让你对响应式编程产生新的理解。RxJS ...

  • 01RxJS-响应式编程类库

    rxjs-响应式编程类库)RxJS官网[https://rxjs.dev/] RxJS(Reactive Exte...

  • Rxjs

    响应式编程简介 Rxjs概念 Rxjs全称Reactive Extension for JavaScript,Ja...

  • Rxjs认知-001

    Day01 认识Rxjs 首先来理解几个概念: 响应式编程: 一种面向数据流和变化传播的编程范式。在编程语言中很...

  • 用Rxjs的思路来处理PHP的回调地狱

    RxPHP 最近最学习Rxjs,rxjs也叫响应式编程,在React和Angluer2使用的比较广泛,还有一点就是...

  • 介绍RxJS在Angular中的应用

    RxJS是一种针对异步数据流编程工具,或者叫响应式扩展编程;可不管如何解释RxJS其目标就是异步编程,Angula...

  • Rxjs响应式编程

    关于Rxjs的现状 鉴于响应式编程近几年才开始真正流行,而且响应式的理念也并不是在所有领域都深得人心,对于不是特别...

  • 响应式编程和RxJS介绍

    响应式编程是一种面向数据流和变化传播的编程范式。面向变化传播的编程就是看最初的数据是否会随着后续对应变量的变化而变...

网友评论

      本文标题:[译]理解响应式编程和RxJS

      本文链接:https://www.haomeiwen.com/subject/ggwiuxtx.html