【问题标题】:How to correctly subscribe to Changed sequence of ReactiveObject?如何正确订阅 ReactiveObject 的更改序列?
【发布时间】:2017-07-10 20:32:17
【问题描述】:

关于 ReactiveUi 的另一个问题。我有一个用于编辑表单的 ViewModel。模型是 ReactiveObject。我只想在对象发生更改时启用保存命令。我的尝试:

var canSaveCommand =
        this.WhenAnyValue(vm => vm.CurrentClient)
            .Where(client => client != null)
            .Select(client =>
                client.Changed
            )
            .Any();

但是当表单出现时,SaveCommand 已经启用。我的错在哪里?

【问题讨论】:

    标签: linq system.reactive reactive-programming reactiveui


    【解决方案1】:

    您想使用 Switch 而不是 SelectMany。 SelectMany 不会取消订阅前一个客户端。它将合并来自所有客户端的事件。 Switch 在订阅下一个客户端之前取消订阅前一个客户端。

     var canSaveCommand =
                this.WhenAnyValue(vm => vm.CurrentClient)
                    .Where(client => client != null)
                    .Select(client =>
                        client.Changed
                    )
                    .Switch()
                    .Any();
    

    例如下面的代码就很清楚了。假设我们有一个名为AudioChannel 的类,它生成我们可以处理的音频帧并将其发送到扬声器。

     public class IAudioChannel {
         public IObservable<AudioFrame> AudioFrameObservable {get;} 
     }
    

    然后我们可能有一个用户可以选择的音频节点列表,但我们只希望向扬声器发送最新的音频。下面的类将当前选择的音频节点作为可观察对象提供。

    public class AudioListViewModel {
        public class IObservable<IAudioChannel> CurrentAudioChannelObservable {get;}
    
    }
    

    现在考虑下面的代码

    AudioListViewModel viewModel;
    
    viewModel
        .CurrentAudioChannelObservable
        .SelectMany(current=>current.AudioFrameObservable)
        .Subscribe(frame=>frame.Play());
    

    对比

    AudioListViewModel viewModel;
    
    viewModel
        .CurrentAudioChannelObservable
        .Select(current=>current.AudioFrameObservable)
        .Switch()
        .Subscribe(frame=>frame.Play());
    

    在第一个版本中,随着我们更改音频节点的选择,我们添加了越来越多的订阅。音频输出很快就变成了混杂的混音通道。在第二个版本中,一次只订阅一个频道,音频输出只播放单个频道的输出。

    很多人在开始使用 RX 时都会犯这个错误。例如,我发现一个bug in the ReactiveUI framework 使用 SelectMany 而不是 Switch。

    然而

    在 ReactiveUI 中有一个内置的方法可以清晰地实现这一点

    实际上还有另一种方法可以实现您想要的,我将它放在另一个答案中只是为了向您展示如何使用 ReactiveUI。

    var canSaveCommand =
            this
              .WhenAnyObservable(vm => vm.CurrentClient.Changed)
              .StartWith(false);
    

    请注意,尽管您应该以 false 开头以确保在没有可观察对象可开始时存在值,但不必明确处理 null。

    WhenAnyObservable

    WhenAnyObservable 的行为很像 Rx 运算符 CombineLatest,在 它监视一个或多个 observables 并允许您定义一个 基于每个的最新值的投影。 WhenAnyObservable 与 CombineLatest 的不同之处在于它的参数是表达式, 而不是直接引用目标 observables。的影响 这个区别是WhenAnyObservable设置的手表不是 与当时存在的特定可观察实例相关联 订阅。也就是说,表达式所指向的 observable 可以 稍后替换,新的 observable 的结果仍然是 捕获。这可以派上用场的一个例子是当一个视图 想要观察视图模型上的可观察对象,但视图模型可以 在视图的生命周期内被替换。而不是需要 每次更改视图模型后重新订阅目标可观察对象, 您可以使用 WhenAnyObservable 指定要观看的“路径”。这 允许您在视图中使用单个订阅,而不管 目标视图模型的生命周期。

    【讨论】:

    • 问题改变的是标准事件包装器 - IObservable> 并且需要转换为布尔值。就我而言,我终于在 VM HasChanges 中添加了另一个可观察的属性,并在 any() 的订阅中设置。
    • P.S. ReactiveUI 在 WPF 中看起来很棒,但它有很多陷阱和隐含的奇异性。有时我想吐口水离开他。 :)
    • “隐式奇点” ??
    • ...隐式特性/功能。
    • 我还是不明白你的意思对不起。能否举个例子。
    【解决方案2】:

    尝试将您的 Select 更改为 SelectMany。然后,这将为您提供要传递给 Any 的更改的 Observable,而不是要传递给 Any 的更改的 Observable 的 Observable。

    【讨论】:

    • SelectMany 不会从以前的客户端退订。它将合并来自所有客户端的事件并给您带来一些讨厌的错误。
    • 如果您明确指出只有一个值可用,那么最好使用Task&lt;T&gt;,即提供一种方法Task&lt;Client&gt; GetClientAsync()。对于属性,假设它们总是可以更改会更安全,如果它们不在软件的v1 中,那么它们可能会在v9 中更改,而你的假设会烧毁你。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-23
    • 1970-01-01
    • 2015-04-10
    • 2019-11-27
    • 2018-02-21
    • 2023-03-26
    相关资源
    最近更新 更多