ReactiveUI 命令取消订阅

ReactiveUI command unsubscription

我有 2 个视图和 2 个视图模型:

第一个视图:

public partial class FirstView : Page
{
    FirstViewModel ViewModel;
    public FirstView()
    {
        ViewModel = new FirstViewModel();
        ViewModel.ShowSecondView.Subscribe(_ =>
        {              
            NavigationService.Navigate(new SecondView(ViewModel.ChildViewModel));
        });            

        this.DataContext = ViewModel;
        InitializeComponent();            
    }        
}

第一个视图模型:

public class FirstViewModel
{
    SecondViewModel ChildViewModel;
    public ReactiveCommand<Unit, Unit> ShowSecondView { get; set; }
    public FirstViewModel()
    {
        ChildViewModel = new SecondViewModel();
        ShowSecondView = ReactiveCommand.Create(() => 
        {
             ChildViewModel.Reconfigure(...);  
        });          
    }        
}

第二个视图:

public partial class SecondView : Page
{
    SecondViewModel ViewModel;
    public SecondView(SecondViewModel viewModel)
    {
        ViewModel = viewModel;
        ViewModel.GoBack.Subscribe(_ => 
        {
            DoSomethingHard();
            if(NavigationService != null)  NavigationService.GoBack();
        });
        this.DataContext = ViewModel;
        InitializeComponent();            
    }        
}

第二个视图模型:

public class SecondViewModel
{
    public ReactiveCommand<Unit, Unit> GoBack { get; set; }
    public FirstViewModel()
    {
        VeryLongInitialization();
        GoBack = ReactiveCommand.Create(() => { });          
    }        
    public void Reconfigure(...)
    { ... }
}

所以,当我 运行 FirstViewModel.ShowSecondView 几次和 运行 SecondViewModel.GoBack 几次时,DoSomethingHard() 也会在每个创建的 SecondView 上执行几次。

为什么我要在 FilstViewModel 中创建一次 ChildViewModel?因为创建 SecondViewModel 需要很长时间。而且我不会每次都重新创建 SecondViewModel 而只是重新配置它。

我的问题是如何在 SecondView 中取消订阅 ViewModel.GoBack.Subscribe

P.S。也许我不应该在 FirstView 中重新创建 SecondView,而是像 SecondViewModel?

一样重新配置它

更新 1(感谢 Julien Mialon)

我添加 IDisposable goBackSubscr 并且有效!我实施它是否正确?

public partial class SecondView : Page
{
    SecondViewModel ViewModel;
    IDisposable goBackSubscr;
    public SecondView(SecondViewModel viewModel)
    {
        ViewModel = viewModel;
        goBackSubscr = ViewModel.GoBack.Subscribe(_ => 
        {
            DoSomethingHard();
            if(NavigationService != null)  NavigationService.GoBack();
            goBackSubscr.Dispose();
        });
        this.DataContext = ViewModel;
        InitializeComponent();            
    }        
}

在您的视图中使用 WhenAcitvated: 在页面的构造函数中(必须是 IViewFor):

 this.WhenActivated(
     disposables =>
     {
         ViewModel.Command.Subscribe(...).(disposables);
     });

subscribe 方法return一个IDisposable,你应该存储它并在你想取消订阅时释放它。

我想我知道你想做什么。如果我是对的,您正在尝试创建某种向导或显示一个视图模型的东西,该视图模型可以移动到下一个,然后可以先移动?如果是这种情况,那么也许您想重新考虑自己的做法。 例如,为什么不在包含两个视图模型的视图模型中管理前后按钮,并让子视图模型仅在首次需要时创建一次。然后,您的导航按钮可以根据逻辑确定当前打开和启用的视图模型。 在 Wizard 类型的场景中,考虑将您的子视图模型基于可能具有 属性 或名为 CanMoveNext() 和 CanMovePrior() 之类的方法的 Base 视图模型。在内部,如果准备就绪,这可以 return 为真。容易多了。

感谢@Krzysztof Skowronek

我再次阅读 IViewFor 并解决了我的问题

public partial class SecondView : Page, IViewFor<SecondViewModel>
{
    public SecondViewModel ViewModel { get; set; }
    object IViewFor.ViewModel { get => ViewModel; set { ViewModel = (SecondViewModel)value; } }
    public SecondView(SecondViewModel viewModel)
    {
        ViewModel = viewModel;
        this.WhenActivated(disposables =>
        {
            ViewModel.GoBack.Subscribe(_ =>
                {
                    DoSomethingHard();
                    if (NavigationService != null) NavigationService.GoBack();
                })
                .DisposeWith(disposables);               

            this.WhenAnyValue(p => p.ViewModel)
                .BindTo(this, x => x.DataContext)
                .DisposeWith(disposables);
        });        
    InitializeComponent();            
    }        
}

我还建议通过激活和停用 ViewModel 来启用和禁用命令。

使用小帮手:

    public static class DisabledCommand
    {
        public static readonly ReactiveCommand<Unit, Unit> Instance
            = ReactiveCommand.Create<Unit, Unit>(_ => Unit.Default, Observable.Return(false));
    }

你可以这样做:

public sealed class MyViewModel : ReactiveObject, IActivateableViewModel
{

    public MyViewModel()
    {
        this.WhenActivated(d =>
        {
            // Observable that returns boolean to define if button is enabled
            // ie. Observable.Create() or this.WhenAnyValue(...)
            var canDoSomething = ...;

            // enable command when VM gets activated
            DoSomething = ReactiveCommand.Create<Unit, Unit>(_ => Unit.Default, canDoSomething);

            // disable command when VM gets disabled
            Disposable.Create(() => DoSomething = DisabledCommand.Instance).DisposeWith(d);

            DoSomething
                .Select(_ => this) // select ViewModel
                .Do(vm =>
                { 
                    // ...
                })
                .ObserveOn(RxApp.MainThreadScheduler)
                .ExecuteOn(RxApp.TaskPoolScheduler)
                .Subscribe()
                .DisposeWith(d);
        });
    }

    [Reactive] public ReactiveCommand<Unit, Unit> DoSomething { get; set; } = DisabledCommand.Instance;
    public ViewModelActivator Activator { get; } = new ViewModelActivator();
}

那么在你看来:

public partial class MyView : Page, IViewFor<MyViewModel>
{
    public MyView()
    {
        InitializeComponent();

        this.WhenActivated(d =>
        {
            if(ViewModel == null) return;

            IDisposable current = Disposable.Empty;
            ViewModel.WhenAnyValue(vm => vm.DoSomething)
                .Select(_ => ViewModel)
                .Subscribe(vm => 
                {
                    current.Dispose();
                    current = vm.DoSomething
                        .Select(_ => vm)
                        .Do(vm_ =>
                        {
                            // do something on the UI
                        })
                        .ObserveOn(RxApp.MainThreadScheduler)
                        .SubscribeOn(RxApp.MainThreadScheduler)
                        .Subscribe();
                })
                .ObserveOn(RxApp.MainThreadScheduler)
                .SubscribeOn(RxApp.MainThreadScheduler)
                .Subscribe()
                .DisposeWith(d);

              Disposable.Create(() => current.Dispose()).DisposeWith(d);
        });
    }

    object IViewFor.ViewModel
    {
        get => ViewModel;
        set => ViewModel = value as MyViewModel;
    }

    public MyViewModel ViewModel
    {
        get => DataContext as MyViewModel;
        set => DataContext = value;
    }
}