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

How to chain subscriptions on Rx.Observabale? (refactor)

如果我有一个 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)

...但其中 none 有效。重构它的最佳方法是什么?


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>

您应该记住,RxJS 的真正强大功能之一在于运算符组合。

与其尝试 forEach()/subscribe() 遍历所有内容(您可以使用传统的 javascript 数组来完成,而无需包含 Rx),您应该尝试思考事件在发生时是如何转换和操作的沿着管道前进。

以下只是您如何通过单个管道完成此操作的一个示例:

  //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)并为我们匹配的元素投射可观察到的事件。
  4. 接下来我们通过使用 tap 来应用副作用。
  5. Merge 将这两个流合并为一个流,这主要是为了让订阅返回到顶层。
  6. 最后订阅整个链,然后当您处理返回的 Disposable 时,它也会清理所有内部流。

这是完整的工作示例:

(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();
    
    
    
})();
<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>