Javascript RxJS:一个可观察对象,它将自己的旧值作为输入
我正在尝试实现如下内容:Javascript RxJS:一个可观察对象,它将自己的旧值作为输入,javascript,rxjs,reactivex,Javascript,Rxjs,Reactivex,我正在尝试实现如下内容: // This obviously doesn't work, because we try to refer to `a` before it exists. // c and m are some Observables. const aSampled = a.pipe( rxjs.operators.sample(c), rxjs.operators.startWith(aInitial) ); const a = m.pipe( rxjs.opera
// This obviously doesn't work, because we try to refer to `a` before it exists.
// c and m are some Observables.
const aSampled = a.pipe(
rxjs.operators.sample(c),
rxjs.operators.startWith(aInitial)
);
const a = m.pipe(
rxjs.operators.withLatestFrom(aSampled),
map(([mValue, oldAValue]) => {
// Do something.
}),
rxjs.operators.startWith(aInitial)
);
这显然是不可编译的胡说八道,因为我们试图在创建之前对
进行采样,但希望它能明确我的意图:从a
发出的每个值都应该依赖于a
先前发出的一个旧值。a
的哪个旧值取决于c
最后一次发射东西的时间。这有点像在a
上成对调用a
,除了我不想要最后两个值,而是最新的和后面的另一个值
请注意,如果不是针对startWith(aininitial)
位,这甚至不是一个定义良好的问题,因为a
发出的第一个值将循环定义,并参考自身。但是,只要第一个值a
是单独指定的,构造就具有数学意义。我只是不知道如何以干净的方式在代码中实现它。我的直觉是,写一个定制的主题会有一个冗长的方法,但是更优雅的东西会非常受欢迎
为了使这更具体一点,在我的用例中,我正在处理一个点击并拖动到平移类型的UI元素m
是可观察到的mousemove
事件,c
是可观察到的mousedown
事件a
将根据光标所在位置以及单击时a
的值不断变化。您可以根据行为主题创建一个名为previous subject的新主题
行为主体的来源在这里
因此,让我们创建一个前一个主题,该主题与前一个值一起发出当前值
import { Subject } from 'rxjs';
import { Subscriber } from 'rxjs';
import { Subscription } from 'rxjs';
import { SubscriptionLike } from 'rxjs';
import { ObjectUnsubscribedError } from 'rxjs';
export class PreviousSubject<T> extends Subject<T[]> {
_value: T[];
constructor(value: T, previous?: T) {
super();
this._value = [value, previous];
}
get value(): T[] {
return this.getValue();
}
/** @deprecated This is an internal implementation detail, do not use. */
_subscribe(subscriber: Subscriber<T[]>): Subscription {
const subscription = super._subscribe(subscriber);
if (subscription && !(<SubscriptionLike>subscription).closed) {
subscriber.next(this._value);
}
return subscription;
}
getValue(): T[] {
if (this.hasError) {
throw this.thrownError;
} else if (this.closed) {
throw new ObjectUnsubscribedError();
} else {
return this._value;
}
}
next(value: T): void {
this._value = [value, this._value[0]];
super.next(this._value);
}
}
经过一番思考,下面是我自己的解决方案,以及一个简单的工作示例:
// Setup for MWE.
// Note that c just emits once a second, the actual values of it don't matter.
const c = rxjs.interval(1000).pipe(rxjs.operators.take(2));
const m = rxjs.interval(300).pipe(rxjs.operators.take(9));
const aInitial = -1;
// Initialize a.
const a = new rxjs.BehaviorSubject();
a.next(aInitial);
// For testing, log the values a emits.
a.subscribe((val) => {
console.log(`a: ${val}`);
});
// Sample a at each emission of c.
const aSampled = a.pipe(
rxjs.operators.sample(c),
rxjs.operators.startWith(aInitial),
);
// Every time m emits, get also the latest sampled value of a, and do something
// with the two.
m.pipe(
rxjs.operators.withLatestFrom(aSampled),
rxjs.operators.map(([mValue, oldAValue]) => {
// In place of actually computing something useful, just log the arguments
// and for the sake of demonstration return their product.
console.log(`m: ${mValue}, old a at last c-emission: ${oldAValue}`);
return mValue*oldAValue;
}),
).subscribe(a); // Route the output back into a.
输出
a: -1
m: 0, old a at last c-emission: -1
a: 0
m: 1, old a at last c-emission: -1
a: -1
m: 2, old a at last c-emission: -1
a: -2
m: 3, old a at last c-emission: -2
a: -6
m: 4, old a at last c-emission: -2
a: -8
m: 5, old a at last c-emission: -2
a: -10
m: 6, old a at last c-emission: -10
a: -60
m: 7, old a at last c-emission: -10
a: -70
m: 8, old a at last c-emission: -10
a: -80
诀窍是将a
设置为主题,允许我们首先发出其初始值,然后将其连接到管道,该管道的输入之一是a
的旧采样值。普通可观察对象不允许这样做(AFAIK),因为您必须在创建时定义它们的所有输入。不过不需要cusom主题类。如果我理解正确,基本事件流是mousemove
和mousedown
根据这些事件流,您必须计算一个新的流a
,该流以与mousemove
相同的频率发出,但其数据是基于鼠标当前位置和上次mousedown
发出时a
的值进行计算的结果
因此,如果这是真的,我们可以用以下观察值模拟mousemove
和mousedown
// the mouse is clicked every 1 second
const c = interval(1000).pipe(
tap(cVal => console.log('click', cVal))
);
// the mouse moves diagonally and emits every 200 ms
const m = interval(200).pipe(
map(mVal => [mVal, mVal]),
);
我们需要的是,当鼠标向下
发射时,以某种方式手头有可观察的a
的值。我们怎么能得到这个
假设我们有一个行为主体
,名为value\u of_a
,其初始值为1
,并保存a
的值。如果我们有这样的可观察性,当mousedown
像这样发射时,我们可以得到its值
const last_relevant_a = c.pipe( // when mousedown emits
switchMap(() => value_of_a.pipe( // the control is switched to the Observable value_of_a
take(1), // and we consider only its first value
)),
);
有了m
、mousemove Observable和last\u relevant\u a
我们就有了所需的所有可观测数据。事实上,我们只需结合他们最新的排放量,就可以得到计算a
新值所需的所有元素
const a = combineLatest(m, last_relevant_a)
.submit(
([mouseVal, old_a_val] => {
// do something to calculate the new value of a
}
);
现在唯一要做的就是确保a的value\u
发出a
发出的任何值。这可以通过在a
本身的订阅中调用a
的value\u来实现
把它们缝合在一起,解决方案可能是
const c = interval(1000).pipe(
tap(cVal => console.log('click', cVal))
);
const m = interval(200).pipe(
map(mVal => [mVal, mVal]),
);
const value_of_a = new BehaviorSubject<number>(1);
const last_relevant_a = c.pipe(
switchMap(cVal => value_of_a.pipe(
take(1),
)),
);
const a = combineLatest(m, last_relevant_a);
a.pipe(
take(20)
)
.subscribe(
val => {
// new value of a calculated with an arbitrary logic
const new_value_of_a = val[0][0] * val[0][1] * val[1];
// the new value of a is emitted by value_of_a
value_of_a.next(new_value_of_a);
}
)
const c=间隔(1000)。管道(
点击(cVal=>console.log('click',cVal))
);
常数m=间隔(200)。管道(
映射(mVal=>[mVal,mVal]),
);
_a的常数值_=新行为主体(1);
最后一个相关的常数a=c管道(
开关映射(cVal=>管道的值(
以(1)为例,
)),
);
常数a=组合测试(m,最后一次相关测试);
a、 烟斗(
采取(20)
)
.订阅(
val=>{
//用任意逻辑计算a的新值
常量新值=val[0][0]*val[0][1]*val[1];
//a的新值由a的值u发出
a的值。下一步(a的新值);
}
)
这可能也是expand
操作符的一个用例,但应该对此进行研究。这与我自己发布的解决方案非常相似,但解释得更好。经核准的!