如何正确订阅 ReactiveObject 的 Changed 序列?

How to correctly subscribe to Changed sequence of ReactiveObject?

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

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

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

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

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

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

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

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

然后我们可能有一个音频节点列表,用户可以 select 但我们只希望将最新的音频发送到扬声器。下面的 class 使当前 selected 音频节点作为可观察对象可用。

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());

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

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

但是

在 ReactiveUI 中有一种内置的方式可以清楚地实现这一点

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

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

请注意,虽然您应该从 false 开始,但不必明确处理 null 以确保在没有可用的 observable 开始时存在值。

WhenAnyObservable

WhenAnyObservable acts a lot like the Rx operator CombineLatest, in that it watches one or multiple observables and allows you to define a projection based on the latest value from each. WhenAnyObservable differs from CombineLatest in that its parameters are expressions, rather than direct references to the target observables. The impact of this difference is that the watch set up by WhenAnyObservable is not tied to the specific observable instances present at the time of subscription. That is, the observable pointed to by the expression can be replaced later, and the results of the new observable will still be captured. An example of where this can come in handy is when a view wants to observe an observable on a viewmodel, but the viewmodel can be replaced during the view's lifetime. Rather than needing to resubscribe to the target observable after every change of viewmodel, you can use WhenAnyObservable to specify the 'path' to watch. This allows you to use a single subscription in the view, regardless of the life of the target viewmodel.