【问题标题】:RxJS distinctUntilChanged - object comparisonRxJS distinctUntilChanged - 对象比较
【发布时间】:2022-03-12 16:59:35
【问题描述】:

我有一个对象流,我需要比较当前对象是否与前一个对象不同,在这种情况下会发出一个新值。我发现 distinctUntilChanged 操作符应该完全符合我的要求,但由于某种原因,它永远不会发出除第一个值之外的值。如果我删除 distinctUntilChanged 值会正常发出。

我的代码:

export class SettingsPage {
    static get parameters() {
        return [[NavController], [UserProvider]];
    }

    constructor(nav, user) {
        this.nav = nav;
        this._user = user;

        this.typeChangeStream = new Subject();
        this.notifications = {};
    }

    ngOnInit() {

        this.typeChangeStream
            .map(x => {console.log('value on way to distinct', x); return x;})
            .distinctUntilChanged(x => JSON.stringify(x))
            .subscribe(settings => {
                console.log('typeChangeStream', settings);
                this._user.setNotificationSettings(settings);
            });
    }

    toggleType() {
        this.typeChangeStream.next({
            "sound": true,
            "vibrate": false,
            "badge": false,
            "types": {
                "newDeals": true,
                "nearDeals": true,
                "tematicDeals": false,
                "infoWarnings": false,
                "expireDeals": true
            }
        });
    }

    emitDifferent() {
        this.typeChangeStream.next({
            "sound": false,
            "vibrate": false,
            "badge": false,
            "types": {
                "newDeals": false,
                "nearDeals": false,
                "tematicDeals": false,
                "infoWarnings": false,
                "expireDeals": false
            }
        });
    }
}

【问题讨论】:

  • typeChangeStream 是 Observable 吗?如果不查看创建该 / 的代码,很难判断出了什么问题。
  • 请查看stackoverflow.com/help/mcve 以获取处理“它不起作用”问题的指南。基本上发布一个重现错误的最小可验证示例,并发布预期的行为以及它与当前行为的不同之处。谈论 JSON 字符串化你需要知道它不是检查对象相等性的防弹方法。 {"a" : 2, "b":1} 例如与 {"b":1, "a":2} 不同,但它们是相同的对象
  • 抱歉,我添加了更多代码。我不需要防弹对象相等性检查,我确信在这种情况下对象将是每次我相同的订单。
  • 你解决了这个问题吗?我对 BehaviorSubject 有同样的看法,它发出 prev 和 curr 相同的结果,wtf ..
  • @DanielSuchý IMO 接受对您有用的答案会很好

标签: angular reactive-programming rxjs


【解决方案1】:

我遇到了同样的问题,并通过使用 JSON.stringify 来比较对象来修复它:

.distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b))

如果属性顺序相同,则此代码将起作用,否则可能会中断,因此这里有一个快速修复(注意此方法较慢)

.distinctUntilChanged((a, b) => JSON.stringify(a).split('').sort().join('') === JSON.stringify(b).split('').sort().join(''))

【讨论】:

  • 我正在输入我的产品单位成本,使用相同的逻辑,但现在我一次只能输入 1 个数字,除非我返回并单击该字段。我希望能够一次输入多个数字,这是正常的默认值。我该怎么做
  • 你试过节流或去抖动 rxJS 操作符吗?
  • 当您delete 一个属性并将其添加回来时,这会给您错误的true
  • 我不明白你的情况, JSON.stringify 只会将对象转换为字符串。即使您没有相同的属性顺序,它也不起作用!
  • JSON.stringify({a:1, b:2}) === JSON.stringify({b:2, a:1}) 这是假的,而结果应该是真的
【解决方案2】:

当你的应用程序中有 lodash 时,你可以简单地使用 lodash 的 isEqual() 函数,它会进行深度比较并完美匹配 distinctUntilChanged() 的签名:

.distinctUntilChanged(isEqual),

或者,如果您有 _ 可用(现在不再推荐):

.distinctUntilChanged(_.isEqual),

【讨论】:

  • 你能澄清你在句子中的意思吗:“或者如果你有 _ 可用(现在不再推荐):”因为你只是说如果你的应用程序中有 lodash 你可以利用lodash的isEqual()函数,但不是用“_”的方式使用lodash调用isEqual方法吗??
  • @Bobby_Cheh 当您导入_-Symbol 时,这包括了lodash 的所有 功能,无论您是否使用它。这不能减少到以后真正需要的,以减少构建过程中产生的包大小(这被称为“Tree Shaking”,因为所有未连接的东西都属于:-))。当您只需要您需要的单个功能时,只有这些才能绑定到您的最终程序包:例如import {isEqual} from 'lodash'。现在它是可摇树的,所以你没有使用的 lodash 的所有功能(可能是 lodash 的大部分 ;-) )将不包括在内。
【解决方案3】:

我终于知道问题出在哪里了。问题出在 RxJS 版本中,在 V4 及更早版本中,参数顺序与 V5 不同。

RxJS 4:

distinctUntilChanged = 函数(keyFn,比较器)

RxJS 5:

distinctUntilChanged = 函数(比较器,keyFn)

在今天的每个文档中,您都可以找到 V4 参数顺序,请注意!

【讨论】:

  • 你是如何解决这个问题的?你改变了什么?如果你能说出这一点,那就太好了。
  • @Harsimer 交换比较器和键功能
【解决方案4】:

你也可以包装原来的distinctUntilChanged函数。

function distinctUntilChangedObj<T>() {
  return distinctUntilChanged<T>((a, b) => JSON.stringify(a) === JSON.stringify(b));
}

这让您可以像使用原版一样使用它。

$myObservable.pipe(
  distinctUntilChangedObj()
)

告诫

正如一些评论者指出的那样,这种方法也有几个缺陷。

  1. 如果对象字段的顺序不同,这将失败;
JSON.stringify({a:1, b:2}) === JSON.stringify({b:2, a:1})
// will return false :(
  1. 如果对象有循环引用,这个JSON.stringify 也会抛出错误。例如,对 firebase sdk 中的内容进行字符串化会导致出现以下错误:
TypeError: Converting circular structure to JSON

稳健的解决方案

使用deep-object-diff 库而不是JSON.stringify。这样就解决了上面的问题?

import { detailedDiff } from 'deep-object-diff';

function isSame(a, b) {
  const result = detailedDiff(a, b); 
  const areSame = Object.values(result)
    .every((obj) => Object.keys(obj).length === 0);
  return areSame;
}

function distinctUntilChangedObj<T>() {
  return distinctUntilChanged<T>((a, b) => isSame(a, b));
}

【讨论】:

  • 尽管它可以工作,但它会与管道对象混淆
  • @Frik 怎么样? JSON.stringify() 不应该弄乱/改变任何东西吗?如果您的对象中有 circular json,那么您可能需要使用另一个可以正确地将其字符串化的包npmjs.com/package/circular-json
  • 如果您使用的是打字稿,那么您也可以使用function distinctUntilChangedObj&lt;T&gt;() { return distinctUntilChanged&lt;T&gt;((a, b) =&gt; JSON.stringify(a) === JSON.stringify(b)); }。这将正确保留类型,但前提是您使用 typescript
  • 不错的答案!这将非常有用。
  • 这是真的@TamusJRoyce,还有其他方法也可能失败,例如对象中的循环引用。我已经更新了解决这些问题的答案
【解决方案5】:

来自 RxJS v6+ 有 distinctUntilKeyChanged

https://www.learnrxjs.io/operators/filtering/distinctuntilkeychanged.html

const source$ = from([
  { name: 'Brian' },
  { name: 'Joe' },
  { name: 'Joe' },
  { name: 'Sue' }
]);

source$
  // custom compare based on name property
  .pipe(distinctUntilKeyChanged('name'))
  // output: { name: 'Brian }, { name: 'Joe' }, { name: 'Sue' }
  .subscribe(console.log);

【讨论】:

  • 仅供参考,如果 name 是像 {name: {first: 'Brian'}} 这样的对象,这不起作用,因为它不会比较整个对象。
【解决方案6】:

Mehdi 的解决方案虽然速度很快,但如果不维护订单就无法工作。使用deep-equalfast-deep-equal 库之一:

.distinctUntilChanged((a, b) => deepEqual(a, b))

【讨论】:

  • 有结构化克隆 polyfil/新标准。为什么 lodash 没有structuredEquals 替代品? lodash 在摇树时对编译器不友好
【解决方案7】:

如果 observable 实际上发出同一对象的修改版本,则建议进行深度比较的答案都会失败,因为传递给比较的“最后”值将与当前,每次。

我们使用 BehaviorSubject 解决了这个问题,碰巧每次都将同一个对象传递给 .next()。

在这种情况下,无论你使用什么比较函数,都没有使用默认 distinctUntilChanged 的​​解决方案。

【讨论】:

    【解决方案8】:

    您也可以制作自己的过滤器:

    function compareObjects(a: any, b: any): boolean {
      return JSON.stringify(a) === JSON.stringify(b);
    }
    
    export function distinctUntilChangedObject() {
      return function<T>(source: Observable<T>): Observable<T> {
        return new Observable(subscriber => {
          let prev: any;
          let first = true;
          source.subscribe({
            next(value) {
              if (first) {
                prev = value;
                subscriber.next(value);
                first = false;
              } else if (!compareObjects(prev, value)) {
                prev = value;
                subscriber.next(value);
              }
            },
            error(error) {
              subscriber.error(error);
            },
            complete() {
              subscriber.complete();
            }
          });
        });
      };
    }
    

    【讨论】:

      【解决方案9】:

      如果您可变地更改该值,则其他答案都不起作用。如果你需要使用可变数据结构,你可以使用 distinctUntilChangedImmutable,它会深度复制之前的值,如果没有传入比较函数,则会断言之前的值和当前值深度相等彼此(不是 === 断言)。

      【讨论】:

      • 类构造函数订阅者不能在新 DistinctUntilChangedSubscriber 处没有“new”的情况下调用
      猜你喜欢
      • 1970-01-01
      • 2016-07-16
      • 1970-01-01
      • 2018-08-31
      • 2013-11-01
      • 1970-01-01
      • 2012-04-15
      • 2011-07-09
      相关资源
      最近更新 更多