メインコンテンツまでスキップ

從 CommunityToolkit.Mvvm 遷移

ReactiveUI 和 CommunityToolkit.Mvvm 可以在同一個專案中共存。不需要一次全部換掉,可以漸進式遷移。


共存架構

// 共存:兩種 ViewModel 在同一個專案中
public class SimpleSettingsVM : ObservableObject // CommunityToolkit
{
[ObservableProperty] private string _deviceName;
}

public class RealtimeMonitorVM : ReactiveObject // ReactiveUI
{
[ObservableAsProperty] public double Temperature { get; }
}

安裝時兩個套件都裝:

dotnet add package CommunityToolkit.Mvvm
dotnet add package ReactiveUI.WPF

遷移優先順序

優先遷移(高收益)

這些 ViewModel 遷移到 ReactiveUI 後收益最大:

類型理由
即時監控儀表板大量 Observable 直接用 OAPH 綁定
多設備狀態合併WhenAnyValue + CombineLatest
搜尋/過濾頁面Throttle + DistinctUntilChanged
複雜表單驗證WhenAnyValue 組合驗證邏輯
需要 CanExecute 自動化的ReactiveCommand 自動追蹤

不用遷移(低收益)

類型理由
簡單設定頁面[ObservableProperty] + [RelayCommand] 就夠了
純 CRUD 頁面沒有響應式需求
靜態資訊頁面不需要 Observable

遷移步驟

Step 1:屬性宣告

// Before: CommunityToolkit
public partial class DeviceVM : ObservableObject
{
[ObservableProperty]
private double _temperature;

[ObservableProperty]
private bool _isConnected;
}

// After: ReactiveUI
public class DeviceVM : ReactiveObject
{
[Reactive] public double Temperature { get; set; }
[Reactive] public bool IsConnected { get; set; }
}

Step 2:命令

// Before: CommunityToolkit
[RelayCommand(CanExecute = nameof(CanConnect))]
private async Task Connect()
{
await client.ConnectAsync();
IsConnected = true;
}

private bool CanConnect() => !IsConnected && !string.IsNullOrEmpty(DeviceIp);

// 需要手動呼叫
partial void OnDeviceIpChanged(string value) => ConnectCommand.NotifyCanExecuteChanged();
partial void OnIsConnectedChanged(bool value) => ConnectCommand.NotifyCanExecuteChanged();

// After: ReactiveUI
var canConnect = this.WhenAnyValue(
x => x.IsConnected,
x => x.DeviceIp,
(connected, ip) => !connected && !string.IsNullOrEmpty(ip));

ConnectCommand = ReactiveCommand.CreateFromTask(async () =>
{
await client.ConnectAsync();
IsConnected = true;
}, canConnect); // CanExecute 自動追蹤,不需手動通知

Step 3:衍生屬性

// Before: CommunityToolkit — 手動在每個相關屬性的 setter 觸發
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(CanStart))]
private bool _isConnected;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(CanStart))]
private bool _isCalibrated;

public bool CanStart => IsConnected && IsCalibrated;

// After: ReactiveUI — 響應式衍生
this.WhenAnyValue(x => x.IsConnected, x => x.IsCalibrated,
(c, cal) => c && cal)
.ToPropertyEx(this, x => x.CanStart);

Step 4:接入 Observable 資料流

這是 ReactiveUI 最大的優勢——CommunityToolkit 做不到的事:

// CommunityToolkit 需要在 Subscribe 回呼中手動設定屬性
deviceStream.Subscribe(data =>
{
Temperature = data.Temperature; // 手動賦值
Pressure = data.Pressure; // 手動賦值
});

// ReactiveUI — Observable 直接綁定為屬性
deviceStream
.Select(d => d.Temperature)
.ToPropertyEx(this, x => x.Temperature);

deviceStream
.Select(d => d.Pressure)
.ToPropertyEx(this, x => x.Pressure);

Step 5:生命週期管理

// Before: 手動管理 CompositeDisposable
public class MonitorVM : ObservableObject, IDisposable
{
private readonly CompositeDisposable _disposables = new();

public void Initialize()
{
stream.Subscribe(...).DisposeWith(_disposables);
}

public void Dispose() => _disposables.Dispose();
}

// After: WhenActivated 自動管理
public class MonitorVM : ReactiveObject, IActivatableViewModel
{
public ViewModelActivator Activator { get; } = new();

public MonitorVM()
{
this.WhenActivated(d =>
{
stream.Subscribe(...).DisposeWith(d);
// View 離開時自動 Dispose
});
}
}

常見問題

Q: 混用時 DI 怎麼設定?

兩個框架的 DI 不衝突。ReactiveUI 用 Locator.CurrentMutable 或標準 IServiceCollection

// 標準 DI — 兩種 ViewModel 混用
services.AddTransient<SimpleSettingsVM>(); // CommunityToolkit
services.AddTransient<RealtimeMonitorVM>(); // ReactiveUI

Q: XAML 綁定方式需要改嗎?

不需要。標準的 {Binding} 語法對兩個框架都有效。ReactiveUI 的 Bind() / OneWayBind() 是可選的強型別替代方案。

Q: 效能有差異嗎?

ReactiveUI 因為 Rx 管道有些微開銷,但在 UI 場景中可忽略。真正的差異在於開發效率——即時數據場景用 ReactiveUI 程式碼量大幅減少。


延伸閱讀