为什么使用“WhenAnyValue”而不是“RaisePropertyChanged”?

问题描述 投票:0回答:1

我一直在阅读 Avalonia 的内容,这个示例 显示了添加属性,然后在视图模型构造函数中使用“WhenAnyValue”来引发“RaisePropertyChanged”事件。

重点是什么?

为什么不在属性的 setter 中使用“RaisePropertyChanged”? “WhenAnyValue”方法有什么好处,因为在 setter 中引发事件更加干净。

design-patterns software-design reactiveui avalonia
1个回答
0
投票

WhenAnyValue 的目的

对于小型项目,所有与业务逻辑稍微脱离的内容都写在

MainWindowViewModel
中,这种方法的用例确实看起来令人困惑。但随着项目规模的扩大,有必要将依赖项注入到
MainWindowViewModel
中,而不是将大部分内容塞进单个类中。

因此,如果您的项目正在利用依赖注入,您将需要这个

WhenAnyValue
方法来观察注入到
MainWindowViewModel
中的其他类中属性的变化,并通知其他注入的东西对变化做出反应。 这是一个例子:

示例

假设您有 2 个复杂的类,

ReactiveCustomer
ReactiveBartender
注入到您的
MainWindowViewModel
中。

MainWindowViewModel 类定义

// MainWindowViewModel class definition
// Contains an instance of ReactiveCustomer and ReactiveBartender as public fields
using System;
using ReactiveUI;

namespace WhyUseWhenAnyValue.ViewModels;

public class MainWindowViewModel : ReactiveObject
{
    public MainWindowViewModel()
    {
        AnReactiveCustomer = new ReactiveCustomer("Rich Ash", 15);
        AnReactiveBartender = new ReactiveBartender();

        // create an observable by WhenAnyValue that observes the field Age of AnReactiveCustomer
        // whenever AnReactiveCustomer's age changes, run the observer in Subscribe
        this.WhenAnyValue(x => x.AnReactiveCustomer.Age).Subscribe(
            (int newCustomerAge) => { 
                AnReactiveBartender.GreetingMessage = newCustomerAge ? $"Hello {AnReactiveCustomer.Name}, here is the menu for cocktail." : $"Hello {AnReactiveCustomer.Name}, you can only have mocktails and juice";
            }
        );
    }

    public ReactiveCustomer  AnReactiveCustomer { get; }
    public ReactiveBartender AnReactiveBartender { get; }

}

AnReactiveBartender
如何迎接顾客取决于
AnReactiveCustomer
的年龄是否在18岁以上。以下是这两个类别的定义。请注意,它们都继承了ReactiveObject。

ReactiveBartender 类定义

// ReactiveBartender class definition. Please note that it also inherits ReactiveObject
// This class is a member of ViewModel namespace

namespace WhyUseWhenAnyValue.ViewModels;

public class ReactiveBartender : ReactiveObject
{
    public ReactiveBartender()
    {
        _greetingMessage = "Default greeting message";
    }
    public string GreetingMessage 
    {
        get => _greetingMessage;
        set => this.RaiseAndSetIfChanged(ref _greetingMessage, value);
    }
    private string _greetingMessage;
}

ReactiveCustomer 类定义

// ReactiveCustomer class definition. Please note that it also inherits ReactiveObject
// This class is a member of ViewModel namespace which 
// wraps around another class Customer defined in Models namespace

using System.Reactive;
using ReactiveUI;
using WhyUseWhenAnyValue.Models;

namespace WhyUseWhenAnyValue.ViewModels;


public class ReactiveCustomer : ReactiveObject
{
    public ReactiveCustomer(string name, int age)
    {
        _customer = new Customer(name, age);
        GrowUpCommand = ReactiveCommand.Create(_reactiveGrowUp);
    }
    public int Age
    {
        get => _customer.Age;
        set => this.RaiseAndSetIfChanged(ref _customer.Age, value);
    }
    public string Name
    {
        get => _customer.Name;
    }
    public ReactiveCommand<Unit, Unit> GrowUpCommand { get; }
    private void _reactiveGrowUp()
    {
        _customer.GrowUp();
        this.RaisePropertyChanged(nameof(_customer.Age));
    }
    // class Customer is defined in the Models namespace, containing tons of business logic
    private Customer _customer;
}

ReactiveCustomer
公开了一个反应式命令
GrowUpCommand
,将其年龄增加1。每当客户的年龄增加时,都会引发一个PropertyChanged事件,因为
RaiseAndSetIfChanged
字段的设置器中有一个
Age

为什么 RaisePropertyChanged 不合适

调酒师的问候语取决于顾客是否超过饮酒年龄。但是他/她无法接收在

PropertyChanged
类中引发的
ReactiveCustomer
事件,因为
ReactiveBartender
不包含观察
ReactiveCustomer
的机制。 GrowUpCommand 运行 100 次后,顾客年龄为 115 岁,调酒师仍然认为他/她低于饮酒年龄。除非我们从客户和调酒师都居住的
MainWindowViewModel
明确通知调酒师更改。

看一下

MainWindowViewModel
的构造函数,有一个由
WhenAnyValue
创建的可观察对象,它观察
Age
AnReactiveCustomer
属性。当 AnReactiveCustomer 的年龄更改时,
Age
的 setter 会引发 PropertyChanged 事件。可观察对象接收此事件并运行
Subscribe
方法中定义的内容。在
Subscribe
方法中存在所谓的
IObserver
,它是一个 lambda,定义接收到
PropertyChanged
事件后要执行的操作。在这种情况下,观察者检查
AnReactiveCustomer
的年龄是否大于18岁,并相应地设置
GreetingMessage
AnReactiveBartender

总而言之,以下是在

Age
AnReactiveCustomer
更改时发生的一系列操作:

  1. PropertyChanged 事件在
    Age
    setter
  2. 中引发
  3. MainWindowViewModel
    中的IObservable接收事件
  4. IObserver根据客户的新年龄是否在18岁以上来设置
    AnActiveBartender
    的问候语信息
  5. PropertyChanged 事件在
    AnActiveBartender
    GreetingMessage
    setter
  6. 中引发
  7. UI 更新绑定到调酒师问候消息的问候消息文本框。

查看定义

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="using:WhyUseWhenAnyValue.ViewModels"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="WhyUseWhenAnyValue.Views.MainWindow"
        x:DataType="vm:MainWindowViewModel"
        Icon="/Assets/avalonia-logo.ico"
        Title="WhyUseWhenAnyValue">

    <Design.DataContext>
        <!-- This only sets the DataContext for the previewer in an IDE,
             to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
        <vm:MainWindowViewModel/>
    </Design.DataContext>
    <StackPanel>
        <TextBlock Text="{Binding AnReactiveBartender.GreetingMessage}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <Button Content="GrowUpBro" Command="{Binding AnReactiveCustomer.GrowUpCommand}"/>
    </StackPanel>

</Window>

结论

因此,简单地回答您的问题“为什么使用 WhenAnyValue 而不是 RaisePropertyChanged?”,这是因为如果您的

MainWindowViewModel
注入了依赖项,则有必要使用
WhenAnyValue
创建另一个可观察对象来观察一个类的属性实例,并通知其他类实例的更改。
WhenAnyValue
并不意味着要取代
RaisePropertyChanged
,它们都有各自的用例。

© www.soinside.com 2019 - 2024. All rights reserved.