• Reactive UI -- 反应式编程UI框架入门学习(一)


    反应式编程

    反应式编程是一种相对于命令式的编程范式,由函数式的组合声明来构建异步数据流。要理解这个概念,可以简单的借助Excel中的单元格函数。

     

    上图中,A1=B1+C1,无论B1和C1中的数据怎么变化,A1中的值都会自动变化,这其中就蕴含了反应式/响应式编程的思想。

    反应式编程对于数据的处理不关心具体的数据值是多少,只要构建出数据的函数式处理,就能并行的异步处理数据流。

    Reactive UI

    Reactive UI 是一种反应式编程的跨平台MVVM框架,支持Xamarin Forms、Xamarin.iOS、Xamarin.Android、Xamarin.Mac、Tizen、Windows Forms、WPF 和UWP。

    本文对比经典的MVVM框架MVVMLight框架来展示ReactiveUI框架的特殊之处。

    在MVVMLight中,依赖属性和命令的绑定一般都是放在Xaml中,并且大部分情况下不需要给控件定义Name属性。

    1

    这是属于弱绑定,在Reactive UI框架中也提供这样的弱绑定,但Reactive UI框架官方推荐使用后台强绑定方式。

    在强绑定方式中,需要给控件定义他的Name属性。

    1

    在界面后台的cs文件中使用强绑定方式。

    1

    2

    //BtnContent是ViewModel中的属性,btnOpenFile是界面中的控件,并指定控件需要绑定的依赖属性

     this.OneWayBind(ViewModel, vm => vm.BtnContent, vw => vw.btnOpenFile.Content);

     在Reactive UI框架中,提供了单向绑定和双向绑定两种绑定类型,上述代码中的OneWayBind是属于ViewModel->View的单向绑定,另外还有一个API  Bind则是双向绑定。

    this.Bind(ViewModel, vm => vm.BtnContent, vw => vw.btnOpenFile.Content);

    之所以官方推荐这样的绑定方式,是因为框架中提供了一个管理ViewModel生命周期的API WhenActivated,解决了Xaml弱绑定方式带来的内存泄露的可能性。

    在WhenActivated API的函数回调中进行绑定属性和Command,可以同步跟踪View和对应绑定属性的生命周期,避免发生内存泄露。

       this.WhenActivated(dispos => {
                    this.OneWayBind(ViewModel, vm => vm.BtnContent, vw => vw.btnOpenFile.Content).DisposeWith(dispos);
                });

    WhenActivated 会在View被激活时同步调用注册的回调函数,注意,在OneWayBind后面新增了一个API调用DisposeWith,他可以确保当界面被销毁时,对应的viewModel及其绑定的属性和命令也会被销毁。

    类似的,绑定Commond

     this.WhenActivated(dispos => {
                    this.OneWayBind(ViewModel, vm => vm.BtnContent, vw => vw.btnOpenFile.Content).DisposeWith(dispos);
    
                     this.BindCommand(ViewModel,
                   viewModel => viewModel.OpenPage,
                   view => view.openButton)
                   .DisposeWith(disposableRegistration);
                });    

    这样的强绑定相比于Xaml中的弱绑定,会有以下的优势:

    1.提供了ViewModel的生命周期管理,避免内存泄露。

    2.控件和后台属性的对应关系更为直观,提高代码的可阅读性。

    当然也有一定的缺陷,会增加代码量,并且增加View和ViewModel的耦合性。

    定义属性和命令

    在MVVMLight中定义一个带通知的属性和Commond:

            private string content ;
            public string Content
            {
                get { return content; }
                set
                {
                    content = value;
                    RaisePropertyChanged(() => Content);
                }
            }
    
            private RelayCommand openFileCommand = null;
            public RelayCommand OpenFileCommand
            {
                get { return openFileCommand = openFileCommand ?? new RelayCommand(OpenFile); }
            }

    在ReactiveUI中也通成功了类似RaisePropertyChanged和RelayCommand功能的API,RaiseAndSetIfChanged和ReactiveCommand。

      private string content;
            public string Content
            {
                get { return content; }
                set
                {
                    this.RaiseAndSetIfChanged(ref content,value);
                }
            }
    
            private ReactiveCommand openFileCommand;
            public ReactiveCommand OpenFileCommand
            {
                get { return openFileCommand = openFileCommand ?? ReactiveCommand.Create(() => { }); }
            }

    其中ReactiveCommand的两个Unit,前一个是传入参数,后一个是返回值。ReactiveCommand的定义与MVVMLight大同小异。

    但是在ReactiveUI中,还有更简单方便的定义可通知的属性,使用标记[Reactive]。

     [Reactive]
     public string Content { get; set; }

    以上代码等价于

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

     动态数据集合

    在.Net中,带通知功能的数据集合一般使用ObservableCollection,但是这个类存在一个限制,不支持多线程操作元素,只能在主线程中增加或者删除元素。所以在多线程操作ObservableCollection的时候,一般都需要通过Dispatcher或者线程上下文来推送操作到UI线程。

    针对这个问题,ReativeUI框架提供了更优雅的操作方式,SourceList,SourceCache, ObservableCollectionExtended,都是线程安全的集合,需要和ReadOnlyObservableCollection一起搭配使用,用于创建可绑定的线程安全的数据集合。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    //这是用于View绑定的数据集合

    private readonly ReadOnlyObservableCollection<string> _disks;

     public ReadOnlyObservableCollection<string> Disks => _disks;

    //这里的ObservableCollectionExtended和SourceList作用相同,都是与_disks强关联并创

    //建副本集合,在操作数据的时候,不直接操作_disks或者Disks,而是对DisksSource或

    //DisksSource2进行操作,会自动的同步到_disk集合并更新到绑定的UI,而Disks用于界面绑定。

      public ObservableCollectionExtended<string> DisksSource;

     public SourceList<string> DisksSource2;

    //以下代码是将DiskSource和DiskSource2与_disk建立强关联关系的两种方式

      DisksSource = new();

                DisksSource.ToObservableChangeSet()

                    .Bind(out _disks)

                    .Subscribe();

     DisksSource2 = new SourceList<string>();

     DisksSource2.Connect().Bind(out _disks).Subscribe();

    函数式组合声明

    以一个读取磁盘文件夹信息的小功能为例。

    一般都需要定义一个ObservableCollection的Model集合,在子线程中需要通过Dispatcher操作集合。

     public ObservableCollection FolderModels { get; set; }
            private async Task LoadFolderInfoWithSelectedDiskChanged(string diskName)
            {
                await Task.Run(() => {
    
                    var files = Directory.GetDirectories(diskName);
                    foreach (var fileName in files)
                    {
                        FolderModel folderModel = new FolderModel();
                        DirectoryInfo directoryInfo = new DirectoryInfo(fileName);
                        folderModel.FolderName = directoryInfo.Name;
                        folderModel.CreateTime = directoryInfo.CreationTime;
                        _dispatcher.Invoke(() => {
    
                            FolderModels.Add(folderModel);
                        });
                    }
                });
            }

    而在ReactiveUI 框架中,不需要Dispatcher这个东西,而是需要通过一个辅助类ObservableAsPropertyHelper。

    ObservableAsPropertyHelper 是一个简化 IObservable 和 ViewModel 上的属性之间的互操作的类,为一个普通属性/字段和一个IObservable对象之间建立观察者模式的联系。

    以上代码可以修改成:

      //当前选中的磁盘符号,是一个IObservable对象

      [Reactive]
      public string SelectedDisk { get; set; }

    //使用ObservableAsPropertyHelper包装
    private readonly ObservableAsPropertyHelper> _folderModels;
    //FolderModels可用于强绑定
    public IEnumerable FolderModels => _folderModels.Value;
    
    //将_folderModels和SelectedDisk建立观察者和被观察者联系,构建函数组合式声明,当SelectedDisk改变时,
    //会自动触发所注册的事件并自动给指定的属性FolderModels赋值。
    
       _folderModels = this.WhenAnyValue(s => s.SelectedDisk)
                    .Where(s => !string.IsNullOrWhiteSpace(s))
                    .SelectMany(LoadFolderInfoWithSelectedDiskChanged)
                    .ObserveOn(RxApp.MainThreadScheduler)
                    .ToProperty(this, nameof(FolderModels));//将计算后得到的结果赋值到指定的属性中
    
    private async Task> LoadFolderInfoWithSelectedDiskChanged(string diskName)
            {
                List folderModels = new List();
                var files = Directory.GetDirectories(diskName);
                foreach (var fileName in files)
                {
                    FolderModel folderModel = new FolderModel();
                    DirectoryInfo directoryInfo = new DirectoryInfo(fileName);
                    folderModel.FolderName = directoryInfo.Name;
                    folderModel.CreateTime = directoryInfo.CreationTime;
                    folderModels.Add(folderModel);
                }
                //这个方法中不需要操作FolderModels 只需要把结果返回即可
                await Task.CompletedTask;
                return folderModels;
            }

    其中ObservableAsPropertyHelper包装的对象是可以任何对象,而LoadFolderInfoWithSelectedDiskChanged方法必须要带有结果返回的异步方法,这样就构成了函数式声明的异步数据流。

    本文列了一些ReactiveUI的简单使用,下一篇会通过一个实例代码进一步学习ReactiveUI框架

  • 相关阅读:
    关系抽取(三)实体关系联合抽取:CasRel
    【JavaScript】掌握BOM浏览器对象模型
    数据库管理-第四十期 基于Oracle 19c RAC的IPv6改造(20221019)
    java基础之内部类[31]
    java-php-net-python-银行招聘信息网计算机毕业设计程序
    d.ts你知道多少?
    GRS全球回收标准-未来趋势
    ROS入门
    嵌入式复习题部分(一)
    Java中的字符串
  • 原文地址:https://blog.csdn.net/sinat_40572875/article/details/128062576