读副作用与写副作用
目前为止,我们的副作用都被圈在了应用外部:
function main() {
return {
DOM: Rx.Observable.timer(0, 1000)
.map(i => `Seconds Elapsed ${i}`),
Log: Rx.Observable.timer(0, 2000)
.map(i => 2 * i)
};
}
function DOMDriver(text$) {
text$.subscribe(text => {
$app = document.querySelector('#app');
$app.textContent = text;
});
}
function consoleLogDriver(msg$) {
msg$.subscribe(msg => {
console.log(msg);
});
}
更多时候,我们还需要从外部世界中获得副作用。例如,现在我们需要在点击页面时,重新开始计时,那么,需要完成两件事:
main()
能够接收页面的点击事件流。DOMDriver()
能够输出页面的点击事件流。
function main(DOMSource) {
const click$ = DOMSource;
return {
DOM: DOMSource.flatMapLatest(() =>
Rx.Observable.timer(0, 1000)
.map(i => `Seconds Elapsed ${i}`)
),
Log: Rx.Observable.timer(0, 2000)
.map(i => 2 * i)
};
}
function DOMDriver(text$) {
text$.subscribe(text => {
$app = document.querySelector('#app');
$app.textContent = text;
});
const DOMSource = Rx.Observable.fromEvent(document, 'click');
return DOMSource;
}
function consoleLogDriver(msg$) {
msg$.subscribe(msg => {
console.log(msg);
});
}
const drivers = {
DOM: DOMDriver,
Log: consoleLogDriver
};
function run(main, drivers) {
const sink = main();
Object.keys(drivers).forEach(key => {
drivers[key](sink[key])
});
}
run(main, drivers);
在这里,sink 表示了一个 写副作用 -- write effects,而 DOM source 则表示了一个 读副作用 -- read effetcs。
目前,程序仍然难于运行,因为我们遇到一个 依赖环(Cycle):
const sink = main(DOMSource)
const DOMSource = DOMDriver(sink.DOM)
// DOMSource --> main
// ^ |
// | v
// DOMDriver <-- sink
为了消除这个环,我们可以在 main()
和 DOMDriver()
间增加一个 代理。main()
从代理获取 DOMSource
,而 DOMDriver()
则将输出交给代理。代理可以通过 Rx.Subject
实现,Subject 能向各个观察者广播新的值到来:
const proxyDOMSource = new Rx.Subject();
const sink = main(proxyDOMSource);
const DOMSource = DOMDriver(sink.DOM);
// 订阅 DOMSource,当 DOMSource 有值产生时,将值传递给 proxyDOMSource
DOMSource.subscribe(click$ => proxyDOMSource.onNext(click$));