從 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 程式碼量大幅減少。
延伸閱讀:
- 概觀 — ReactiveUI vs CommunityToolkit 完整比較
- 核心概念 — 遷移後需要掌握的新概念
- 場景 Pattern — 遷移後可以實現的模式