Cài đặt gói thư viện
Để tích hợp ReactiveUI vào dự án, cần cài đặt các gói NuGet phù hợp với nền tảng mục tiêu. Đối với ứng dụng WPF, hãy đảm bảo cài đặt cả gói cốt lõi và gói hỗ trợ riêng cho WPF:
Install-Package ReactiveUI
Install-Package ReactiveUI.WPF
Xây dựng ViewModel
Các lớp ViewModel trong ReactiveUI cần kế thừa từ ReactiveObject để hỗ trợ thông báo thay đổi thuộc tính tự động.
public class UserSessionViewModel : ReactiveObject
{
}
Thuộc tính đọc ghi
Sử dụng phương thức RaiseAndSetIfChanged để cập nhật giá trị và thông báo thay đổi:
private string _emailAddress;
public string EmailAddress
{
get => _emailAddress;
set => this.RaiseAndSetIfChanged(ref _emailAddress, value);
}
Thuộc tính chỉ đọc
Đối với các thuộc tính dẫn xuất từ observable, sử dụng ObservableAsPropertyHelper:
private readonly ObservableAsPropertyHelper<string> _domainName;
public string DomainName => _domainName.Value;
// Trích xuất tên miền khi EmailAddress thay đổi
this.WhenAnyValue(x => x.EmailAddress)
.Where(x => !string.IsNullOrEmpty(x) && x.Contains("@"))
.Select(x => x.Split('@')[1])
.ToProperty(this, x => x.DomainName, out _domainName);
Tối ưu hóa với Fody
Khi sử dụng thêm gói ReactiveUI.Fody, code có thể được viết gọn hơn nhờ weaving tự động:
[Reactive]
public string EmailAddress { get; set; }
public string DomainName { [ObservableAsProperty] get; }
this.WhenAnyValue(x => x.EmailAddress)
.Where(x => !string.IsNullOrEmpty(x))
.Select(x => x.Contains("@") ? x.Split('@')[1] : string.Empty)
.ToPropertyEx(this, x => x.DomainName);
Xử lý lệnh Command
ReactiveUI cung cấp nhiều phương thức tĩnh để tạo ReactiveCommand tùy theo nhu cầu đồng bộ hay bất đồng bộ:
CreateFromObservable()CreateFromTask()Create()CreateCombined()
Lệnh đồng bộ
ReactiveCommand<string, Unit> logCommand = ReactiveCommand.Create<string>(
message => System.Diagnostics.Debug.WriteLine(message));
logCommand.Execute("Debug Info").Subscribe();
Lệnh bất đồng bộ
var fetchCommand = ReactiveCommand.CreateFromTask<Unit, int>(
async _ =>
{
await Task.Delay(TimeSpan.FromSeconds(2));
return 100;
});
fetchCommand.Execute(Unit.Default).Subscribe();
fetchCommand.Subscribe(result => System.Diagnostics.Debug.WriteLine(result));
Điều kiện thực thi
Có thể định nghĩa điều kiện canExecute dựa trên trạng thái của các thuộc tính khác:
var canLogin = this.WhenAnyValue(
x => x.EmailAddress, x => x.Password,
(email, pass) =>
!string.IsNullOrEmpty(email) &&
pass.Length >= 6);
var loginCommand = ReactiveCommand.CreateFromTask(PerformLoginAsync, canLogin);
Liên kết giao diện
Trong code-behind của View (Window, Page, UserControl), khởi tạo ViewModel và gán vào DataContext:
<Window x:Class="ReactiveDemo.ShellView"
...>
<!-- Sử dụng binding XAML truyền thống -->
</Window>
public partial class ShellView : Window
{
public ShellView()
{
InitializeComponent();
DataContext = new UserSessionViewModel();
}
}
Quản lý tập dữ liệu động
Để xử lý danh sách dữ liệu một cách reactive, nên sử dụng thư viện DynamicData đi kèm. Các loại集合 phổ biến là SourceList và SourceCache.
// Danh sách nguồn nội bộ
SourceList<int> _sourceNumbers = new SourceList<int>();
// Collection chỉ đọc để binding
ReadOnlyObservableCollection<int> _filteredItems;
public ReadOnlyObservableCollection<int> FilteredItems => _filteredItems;
// Thao tác trên danh sách
_sourceNumbers.Add(10);
_sourceNumbers.Add(15);
_sourceNumbers.Remove(10);
// Biến đổi và binding
_sourceNumbers.Connect()
.Transform(x => x * 2)
.Filter(x => x % 3 == 0)
.ObserveOn(RxApp.MainThreadScheduler)
.Bind(out _filteredItems)
.Subscribe();
Xác thực dữ liệu đầu vào
Chức năng validation yêu cầu cài đặt thêm gói ReactiveUI.Validation. ViewModel cần implement IValidatableViewModel hoặc kế thừa ReactiveValidationObject.
ReactiveValidationObject đã tích hợp sẵn INotifyDataErrorInfo giúp tương thích với cơ chế validation của WPF.
public class LoginViewModel : ReactiveObject, IValidatableViewModel
{
public ValidationContext ValidationContext { get; } = new ValidationContext();
[Reactive] public string Email { get; set; }
[Reactive] public string Password { get; set; }
public ReactiveCommand<Unit, Unit> Submit { get; }
public LoginViewModel()
{
this.ValidationRule(
viewModel => viewModel.Email,
email => !string.IsNullOrWhiteSpace(email) && email.Contains("@"),
"Email format is invalid");
var isValid = this.IsValid();
Submit = ReactiveCommand.CreateFromTask(async unit => { }, isValid);
}
}
Cách viết ngắn gọn hơn khi kế thừa ReactiveValidationObject:
public class LoginViewModel : ReactiveValidationObject<LoginViewModel>
{
[Reactive]
public string Email { get; set; } = string.Empty;
public LoginViewModel()
{
this.ValidationRule(
x => x.Email,
email => !string.IsNullOrWhiteSpace(email),
"Email field is required.");
}
}
Ghi nhật ký hệ thống
Để ghi log, cần tích hợp thêm các gói như Serilog. Các gói cần thiết bao gồm:
- Serilog
- Splat.Serilog
- Serilog.Sinks.File
Cấu hình Logger trong quá trình khởi động ứng dụng:
using Serilog;
using Splat;
using Splat.Serilog;
using System.Windows;
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Log.Logger = new LoggerConfiguration()
.WriteTo.File("app-log-.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
Locator.CurrentMutable.UseSerilogFullLogger();
}
}
Sử dụng Logger trong các class implements IEnableLogger:
using Splat;
using System.Windows;
public partial class ShellView : Window, IEnableLogger
{
public ShellView()
{
InitializeComponent();
this.Log().Info("ShellView loaded successfully.");
}
}
2023-10-27 09:15:22.123 +00:00 [INF] ShellView loaded successfully.