我一直在尝试将一段代码重构为自定义rxjs运算符,但我无法让它工作。
到目前为止,这是我的自定义运算符:
dtype=object
这里的想法是将新列表与旧列表进行比较,如果两个列表中的最后一项匹配,则export const isLastItemTheSame = (oldValue: any[], key: string, condition: boolean) => {
return condition ? <T>(obsv: Observable<T[]>) => obsv.pipe(
filter(newValue => {
try {
return (oldValue[oldValue.length - 1][key] === newValue[newValue.length - 1][key]);
}
catch(err) {
return false;
}
}),
mapTo(true)
) : <T>(obsv: Observable<T>) => obsv.pipe(ignoreElements());
};
的成功回调不应触发。但是如果这些项目不匹配,那就应该。
我现在遇到的问题是:
subscribe
位不起作用,因为它没有触发成功回调。<T>(obsv: Observable<T>) => obsv.pipe(ignoreElements())
为condition
时,运算符返回布尔值而不是新列表。这使得无法将新列表绑定到订阅成功回调中的true
。我用它像:
this.items
已经注释掉的代码是我尝试重构的代码,这使我很容易看到我想要实现的目标。
我该如何解决这个问题?
答案 0 :(得分:1)
编辑:该答案假定您实际上要与外部引用列表进行比较,而不仅仅是同一个可观察值的先前发出值进行比较。这就是您的问题所要说明的内容,但是如果您要引用先前的值,请查看my second answer。
让我们先进行一些观察:
当条件为true时,运算符将返回布尔值而不是新列表。
这就是您的mapTo(true)
的工作,因此,请忽略它:-)
ignoreElements())位不起作用,因为它不会触发成功回调。
那是因为您不想忽略这些元素,而是在这种情况下只需返回可观察的值即可。
但是,我不会将条件移到运算符中-从逻辑上讲,它与它无关,您无论如何都要将整个逻辑(if的“主体”)传递给运算符。
此外,您的运算符当前缺乏适当的类型签名,并且创建自定义运算符的操作反而不太繁琐,例如使用静态pipe
函数(不过我们在这里甚至不需要)。
最后,如果您希望对通过的每个值进行重新评估,我们不能仅仅静态地传递参考列表,因此我们需要使用供应商。
因此,让我们修复所有这些问题。我对运营商的建议是这样:
import { OperatorFunction } from 'rxjs';
import { filter } from 'rxjs/operators';
export function filterIfLastElementMatches<T extends Record<any, any>>(
previousSupplier: () => T[],
key: keyof T
): OperatorFunction<T[], T[]> {
return filter(value => {
const previous = previousSupplier();
return !previous.length || !value.length || value[value.length - 1][key] !== previous[previous.length - 1][key]
});
}
然后使用它并将其与条件部分结合起来
source$.pipe(
// …
!isReset ? filterIfLastElementMatches(() => this.items, 'eventid') : tap(),
).subscribe(items => { /* … */ });
查看实际情况here。
请注意,这里条件的语义略有不同,因为它将在执行包装方法时(而不是在发出值出现时)进行评估。我认为这在这里并不重要。
答案 1 :(得分:1)
注意:这是第二个答案,但是这次我们假设您要与之前发出的值进行比较,而不是与外部参考列表进行比较。 my other answer中的一般要点仍然适用。
如果这是您需要的机制,我强烈建议您使用此答案,而不是使用外部供应商。
如果要与之进行比较的值应该只是先前发出的值,则可以通过自定义比较器函数对此简单地使用distinctUntilChanged
。如果您坚持将其包装到自定义运算符中,则可能是这样:
import { OperatorFunction } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators';
export function distinctUntilLastElementByKey<T extends Record<any, any>>(
key: keyof T
): OperatorFunction<T[], T[]> {
return distinctUntilChanged((previous, value) =>
previous.length && value.length && value[value.length - 1][key] === previous[previous.length - 1][key]
);
}
您可以在操作here中看到它。
但是,仅将比较器函数重构为它自己的函数并在代码中仅使用
source$.pipe(distinctUntilChanged(createLastItemKeyComparator('eventid')))
答案 2 :(得分:0)
如何:我的意思是-作为骨架-您可以将关键测试扩展到您的需求。
const filterIfLastEqual = () => <T>(source: Observable<T[]>) => {
return new Observable(observer => {
let lastValue: T;
return source.subscribe({
next(x) {
const [nowLast] = x.slice(-1);
if (lastValue !== nowLast) {
console.log('diff', lastValue, nowLast)
observer.next(x);
lastValue = nowLast;
} else {
console.log('skipping', lastValue, nowLast)
}
},
error(err) {
observer.error(err);
},
complete() {
observer.complete();
}
})
})
}
const emitter = new Subject<any[]>();
emitter.pipe(
filterIfLastEqual(),
).subscribe(
v => console.log('received value:', v)
)
// test cases
emitter.next([1, 2, 3, 4]);
emitter.next([1, 2, 3, 4]);
emitter.next([1, 2, 3, 5]);
emitter.next([1, 2, 3, '5']);
您还可以结合使用scan()
(它是有状态的)和distnctUntilChanged()
。逻辑几乎相同。