如何在 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
详细说明这里发生了什么:
- 使用
flatMap
监听definitions
流,每个值d
将作为一个匹配的元素进行操作。
- 然后我们为这两个事件创建侦听器,并创建一个侦听器以遍历匹配的元素。
- 接下来,我们再次使用
flatMap
来捕获每个事件(mouseOver 和 mouseOut)并为我们匹配的元素投射可观察到的事件。
- 接下来我们通过使用
tap
来应用副作用。
Merge
将这两个流合并为一个流,这主要是为了让订阅返回到顶层。
- 最后订阅整个链,然后当您处理返回的
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>
如果我有一个 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
详细说明这里发生了什么:
- 使用
flatMap
监听definitions
流,每个值d
将作为一个匹配的元素进行操作。 - 然后我们为这两个事件创建侦听器,并创建一个侦听器以遍历匹配的元素。
- 接下来,我们再次使用
flatMap
来捕获每个事件(mouseOver 和 mouseOut)并为我们匹配的元素投射可观察到的事件。 - 接下来我们通过使用
tap
来应用副作用。 Merge
将这两个流合并为一个流,这主要是为了让订阅返回到顶层。- 最后订阅整个链,然后当您处理返回的
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>