Reactive UI 共享可观察逻辑

Reactive UI Sharing Observable Logic

如果不满足输入文本的特定条件,我会尝试设置特定的错误消息。

基本上如果输入 url 为空,我想将 ErrorHint 设置为 "Server URL cannot be empty"

如果输入 url 不是以 https 开头,ErrorHint 应该是 "Server URL must start with https"

否则 ErrorHint 应该是一个空字符串。

下面的代码有效,但我觉得我在我的可观察对象中复制逻辑。有更简洁的方法吗?

        // invalid if the server url is empty
        var serverUrlEmptyObservable =
            this.WhenAnyValue(x => x.NewServerUrl)
                .Where(string.IsNullOrWhiteSpace);

        // invalid if the url is not empty but does not start with https
        var serverUrlInvalidPrefixObservable =
            this.WhenAnyValue(x => x.NewServerUrl)
                .Where(x => !string.IsNullOrWhiteSpace(x))
                .Where(x => !x.StartsWith(NewServerUrlRequiredPrefix));

        // valid if the server url is not empty and starts with proper prefix
        var validServerUrlObservable =
            this.WhenAnyValue(x => x.NewServerUrl)
                .Where(x => !string.IsNullOrWhiteSpace(x))
                .Where(x => x.StartsWith(NewServerUrlRequiredPrefix));

        // set error message based on which observable fires
        ErrorHint = Observable.Merge(
            validServerUrlObservable.Select(x => ""), 
            serverUrlInvalidPrefixObservable.Select(x => $"Server URL must start with {NewServerUrlRequiredPrefix}"), 
            serverUrlEmptyObservable.Select(x => "Server URL cannot be empty"));

您可以将所有逻辑放在一个 Select 函数中:

ErrorHint = this.WhenAnyValue(x => x.NewServerUrl)
    .Select(url => 
    {
        if(string.IsNullOrWhiteSpace(url)) 
        {
            return "Server URL cannot be empty";
        }
        if(!url.StartsWith(NewServerUrlRequiredPrefix))
        {
            return $"Server URL must start with {NewServerUrlRequiredPrefix}";
        }
        return "";
    })
    .DistinctUntilChanged();

这只是将每个 NewServerUrl 值映射到适当的错误消息,然后仅在错误消息更改时通知观察者(使用 DistinctUntilChanged)。

创建一个IObservable 并使用ToProperty 方法设置ErrorHint 输出的值属性。这基本上是 "ReactiveUI" 方式的实现方式:

public class ReactiveViewModel : ReactiveObject
{
    private const string NewServerUrlRequiredPrefix = "https";

    public ReactiveViewModel()
    {
        this.WhenAnyValue(x => x.NewServerUrl)
            .Select(_ =>
            {
                if (string.IsNullOrEmpty(NewServerUrl))
                    return "Server URL cannot be empty";
                else if (!NewServerUrl.StartsWith(NewServerUrlRequiredPrefix))
                    return $"Server URL must start with {NewServerUrlRequiredPrefix}";

                return string.Empty;
            }).ToProperty(this, x => x.ErrorHint, out _errorHint);
    }

    private string _newServerUrl;
    public string NewServerUrl
    {
        get { return _newServerUrl; }
        set { this.RaiseAndSetIfChanged(ref _newServerUrl, value); }
    }

    private readonly ObservableAsPropertyHelper<string> _errorHint;
    public string ErrorHint { get { return _errorHint.Value; } }
}