如何在Rx.Observabale上链接订阅? (重构)

时间:2015-06-25 17:11:26

标签: javascript reactive-programming rxjs

如果我有Rx.Observable,如何通过forEach订阅多个功能?下面的代码有效,但这部分对我来说特别不干净:

Rx.Observable.from(definition).forEach(highlight);                   
Rx.Observable.from(definition).forEach(prefix);

我知道我可以创建一个包装函数来调用它们中的这两个,但我喜欢将它们分开的可读性。我喜欢的是做类似于

的事情
Rx.Observable.from(definition).forEach(highlight, prefix)

definition = Rx.Observable.from(definition)
definition.forEach(highlight)
definition.forEach(prefix)

Rx.Observable.from(definition).forEach(highlight).forEach(prefix)

......但这些都没有奏效。重构这个的最佳方法是什么?

JS:

(function commands() {                                                   
  function highlight(node) {                                               
    node.classList.add(hoverClass);                                        
  }                                                                        

  function unhighlight(node) {                                             
    node.classList.remove(hoverClass);                                     
  }                                                                        

  function prefix(node) {                                                  
    node.classList.add(prefixClass);                                       
  }                                                                        

  function unprefix(node) {                                                
    node.classList.remove(prefixClass);                                    
  }                                                                        

  function unprefixAll(nodes) {                                            
    Rx.Observable.from(nodes).forEach(unprefix);                           
  }                                                                        

  var hoverClass  = "hover";                                               
  var prefixClass = "prefixed";                                            
  var $commands  = document.querySelector("#commands");                
  var definitions = Rx.Observable.from($commands.querySelectorAll("dt"))  
    .map(function(_, i) {                                                  
      return $commands.querySelectorAll(                                  
        "dt:nth-of-type("+ (i + 1) +"), dt:nth-of-type("+ (i + 1) +") + dd"
      );                                                                   
    });                                                                    

  definitions.forEach(function (definition) {                              
    Rx.Observable.fromEvent(definition, "mouseover").forEach(function() {  
      definitions.forEach(unprefixAll);                                    
      Rx.Observable.from(definition).forEach(highlight);                   
      Rx.Observable.from(definition).forEach(prefix);                      
    });                                                                    

    Rx.Observable.fromEvent(definition, "mouseout").forEach(function() {   
      Rx.Observable.from(definition).forEach(unhighlight);                 
    });                                                                    
  });                                                                      
})();  

HTML:

<dl id="commands">                                                                          
  <dt class="prefixed">command 1</dt>                                                          
  <dd>does a thing for command 1</dd>   
  <dt>command 2</dt>                                                          
  <dd>does a thing for command 2</dd>    
  <dt>command 3</dt>                                                          
  <dd>does a thing for command 3</dd>    
  <dt>command 4</dt>                                                          
  <dd>does a thing for command 4</dd>                                                                            
  <dt>help</dt>                                                                                
  <dd>Shows all available commands</dd>                                                        
</dl>

1 个答案:

答案 0 :(得分:5)

你应该记住,RxJS的一个真正的力量在于算子构成。

不是试图forEach()/subscribe()通过所有内容(你可以使用传统的javascript数组而不需要包含Rx),而是应该考虑事件在沿着管道传输时如何被转换和操纵。 / p>

以下只是一个如何通过单一管道实现的例子:

  //Gets a subscription which can be used to clean up all the internal streams
  //Use flatMap to flatten the inner streams into a single stream
  var subscription = definitions.flatMap(function (d) {       
      var mouseOver = Rx.Observable.fromEvent(d, "mouseover");
      var mouseOut = Rx.Observable.fromEvent(d, "mouseout");
      var definition = Rx.Observable.from(d);

      //Merge together both mouseOver and mouseOut so we can cancel them together later
      //Use tap to apply side effects.
      return Rx.Observable.merge(mouseOver.flatMap(definition)
                                          .tap(prefix)
                                          .tap(highlight),
                                 mouseOut.flatMap(definition)
                                         .tap(unprefix)
                                         .tap(unhighlight));

 }).subscribe();

修改1

详细说明这里发生了什么:

  1. 使用flatMap收听definitions信息流,每个值d都是一个匹配的操作元素。
  2. 然后我们为两个事件创建侦听器,并且一个迭代匹配的元素。
  3. 接下来,我们再次使用flatMap来捕获每个事件(mouseOver和mouseOut),并为我们的匹配元素项目提供observable。
  4. 接下来,我们通过tap
  5. 来应用副作用
  6. Merge将这两个流合并为一个流,这主要是为了将订阅传递回顶层。
  7. 最后订阅整个链,然后当你处理返回的Disposable时,它也将清理所有内部流。
  8. 以下是完整的工作示例:

    &#13;
    &#13;
    (function commands() {                                                   
      function highlight(node) {                                               
        node.classList.add(hoverClass);                                        
      }                                                                        
    
      function unhighlight(node) {                                             
        node.classList.remove(hoverClass);                                     
      }                                                                        
    
      function prefix(node) {                                                  
        node.classList.add(prefixClass);                                       
      }                                                                        
    
      function unprefix(node) {                                                
        node.classList.remove(prefixClass);                                    
      }                                                                                                                                    
        
      var hoverClass  = "hover";                                               
      var prefixClass = "prefixed";                                            
      var $commands  = document.querySelector("#commands");                
      var definitions = Rx.Observable.from($commands.querySelectorAll("dt"))  
        .map(function(_, i) {                                                  
          return $commands.querySelectorAll(                                  
            "dt:nth-of-type("+ (i + 1) +"), dt:nth-of-type("+ (i + 1) +") + dd"
          );                                                                   
        });
            
      var subscription = definitions.flatMap(function(d) {
          //Declare this stuff up front        
          var mouseOver = Rx.Observable.fromEvent(d, "mouseover");
          var mouseOut = Rx.Observable.fromEvent(d, "mouseout");
          var definition = Rx.Observable.from(d);
                
          return Rx.Observable.merge(mouseOver.flatMap(definition).tap(prefix).tap(highlight),
                                     mouseOut.flatMap(definition).tap(unprefix).tap(unhighlight)).ignoreElements();   
        }).subscribe();
        
        
        
    })();
    &#13;
    <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.5.3/rx.all.js"></script>
    <dl id="commands">                                                                          
      <dt class="prefixed">command 1</dt>                                                          
      <dd>does a thing for command 1</dd>   
      <dt>command 2</dt>                                                          
      <dd>does a thing for command 2</dd>    
      <dt>command 3</dt>                                                          
      <dd>does a thing for command 3</dd>    
      <dt>command 4</dt>                                                          
      <dd>does a thing for command 4</dd>                                                                            
      <dt>help</dt>                                                                                
      <dd>Shows all available commands</dd>                                                        
    </dl>
    &#13;
    &#13;
    &#13;