ReactiveUI 響應式 MVVM 指南
用途:基於 Rx.NET 的跨平台 MVVM 框架,將響應式程式設計融入 UI 架構
NuGet:
ReactiveUI(核心)、ReactiveUI.WPF(WPF 整合)版本:23.2.1
授權:MIT(.NET Foundation 成員)
支援:.NET 8+(WPF、WinUI、MAUI、Avalonia、Blazor)
什麼是 ReactiveUI
ReactiveUI 是建立在 Rx.NET(System.Reactive) 之上的 MVVM 框架。它不只是「另一個 MVVM 框架」,而是讓你用 Observable 流 來表達 UI 的所有互動邏輯——屬性變化、命令執行、資料載入、錯誤處理——全部是可組合的響應式管道。
與 Rx.NET 的關係
System.Reactive (Rx.NET) ← 核心響應式抽象
↑
ReactiveUI ← MVVM 框架層
↑
ReactiveUI.WPF ← WPF 平台綁定
ReactiveUI 不是 Rx.NET 的替代品,而是延伸:
- Rx.NET 提供
IObservable<T>、operators、schedulers - ReactiveUI 提供
ReactiveObject(MVVM base class)、ReactiveCommand、WhenAnyValue(屬性觀察)、ObservableAsPropertyHelper(Observable → 屬性)
如果你已經在用 Rx.NET 做設備資料流處理,ReactiveUI 讓你把同樣的 Observable 管道直接接到 UI 層。
vs CommunityToolkit.Mvvm(公司目前用法)vs Prism
| 項目 | CommunityToolkit.Mvvm | ReactiveUI | Prism |
|---|---|---|---|
| 核心理念 | 輕量、Source Generator | Rx 原生、響應式綁定 | 模組化、導航 |
| 屬性通知 | [ObservableProperty] | RaiseAndSetIfChanged | SetProperty |
| 命令 | [RelayCommand] | ReactiveCommand | DelegateCommand |
| Rx.NET 整合 | 無(需手動橋接) | 原生 | 無 |
| 學習曲線 | 低 | 中高(需要懂 Rx) | 中 |
| 程式碼風格 | 屬性 + Source Generator | 響應式管道 | 傳統 MVVM |
| CanExecute | 手動觸發 NotifyCanExecuteChanged | 自動(Observable 驅動) | 手動 RaiseCanExecuteChanged |
| 生命週期管理 | 手動 Dispose | WhenActivated 自動管理 | 無內建 |
| 適合場景 | 簡單 CRUD、設定頁面 | 即時數據、複雜互動 | 大型模組化系統 |
什麼場景該選哪個
| 場景 | 推薦 | 理由 |
|---|---|---|
| 簡單設定頁面 | CommunityToolkit.Mvvm | 輕量、Source Generator 快速開發 |
| 設備即時監控儀表板 | ReactiveUI | 大量 Observable 資料流直接綁定 UI |
| Recipe 編輯器 | ReactiveUI 或 CommunityToolkit | 看是否需要響應式驗證 |
| 多設備狀態合併顯示 | ReactiveUI | CombineLatest + WhenAnyValue |
| 只有按鈕和表單的頁面 | CommunityToolkit.Mvvm | ReactiveUI 大材小用 |
兩者可以共存。你可以在同一個專案中混用——即時監控 ViewModel 用 ReactiveUI,簡單設定頁面用 CommunityToolkit。見遷移指南。
核心概念一覽
| 概念 | 用途 | 對應 CommunityToolkit |
|---|---|---|
ReactiveObject | ViewModel 基底類別(INPC) | ObservableObject |
WhenAnyValue | 監聽屬性變化 → Observable | 無直接對應 |
ReactiveCommand | 命令(支援 async、CanExecute 自動化) | RelayCommand |
ObservableAsPropertyHelper | Observable → 唯讀屬性 | 無直接對應 |
WhenActivated | 自動管理訂閱生命週期 | 無直接對應 |
響應式綁定流程
ReactiveUI 的精髓是「屬性變更 = Observable 訊號」。一個最常見的情境是:使用者在 TextBox 輸入 IP,透過 WhenAnyValue 推導出 ConnectCommand 的 CanExecute,按鈕啟用狀態自動反映輸入有效性。以最小 WPF 範例的 DeviceMonitorViewModel 為例:
關鍵差異對比 CommunityToolkit.Mvvm:
- CommunityToolkit:屬性 setter 後,必須呼叫
ConnectCommand.NotifyCanExecuteChanged()才會刷新按鈕狀態 - ReactiveUI:
canExecute是 Observable,屬性變更自動走完整條管道,不需要在 setter 裡額外呼叫
WhenAnyValue operator pipeline
WhenAnyValue 只是起點——它把屬性變更轉成 IObservable<T>,之後就能套用任何 Rx operator。下圖以「搜尋去抖動」為例,展示一條典型的 ReactiveUI pipeline:
這條管道等價於以下程式碼:
this.WhenAnyValue(x => x.SearchText)
.Throttle(TimeSpan.FromMilliseconds(300))
.DistinctUntilChanged()
.SelectMany(text => QueryApiAsync(text))
.ObserveOn(RxApp.MainThreadScheduler)
.BindTo(this, vm => vm.Results);
各段 operator 的意圖:
WhenAnyValue:訂閱屬性變更,把 setter 訊號轉成 Observable.Throttle+.DistinctUntilChanged:使用者快速打字時合併多次變更,相同字串不重覆查詢.SelectMany:非同步 API 呼叫,SelectMany會自動取消舊請求.ObserveOn(MainThreadScheduler):把結果 marshal 回 UI 執行緒.BindTo:把 Observable 最終值寫回 ViewModel 屬性,UI 自動更新
安裝
dotnet add package ReactiveUI.WPF # WPF 專案
dotnet add package ReactiveUI.SourceGenerators # 推薦:Source Generator 簡化屬性宣告
ReactiveUI.SourceGenerators 是目前推薦的方式(取代舊的 ReactiveUI.Fody)。它使用 C# Source Generator,支援 [Reactive]、[ObservableAsProperty]、[ReactiveCommand] 屬性。
常用 namespace:
using ReactiveUI;
using ReactiveUI.SourceGenerators;
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Disposables;
最小 WPF 範例
ViewModel
public class DeviceMonitorViewModel : ReactiveObject
{
private string _deviceIp = "";
public string DeviceIp
{
get => _deviceIp;
set => this.RaiseAndSetIfChanged(ref _deviceIp, value);
}
private string _connectionStatus = "未連線";
public string ConnectionStatus
{
get => _connectionStatus;
set => this.RaiseAndSetIfChanged(ref _connectionStatus, value);
}
public ReactiveCommand<Unit, Unit> ConnectCommand { get; }
public DeviceMonitorViewModel()
{
// CanExecute:IP 不為空時才能點連線
var canConnect = this.WhenAnyValue(x => x.DeviceIp,
ip => !string.IsNullOrWhiteSpace(ip));
ConnectCommand = ReactiveCommand.CreateFromTask(
async () =>
{
ConnectionStatus = "連線中...";
await ConnectToDeviceAsync(DeviceIp);
ConnectionStatus = "已連線";
},
canConnect);
}
}
View(XAML)
<Window x:Class="DeviceControl.MainWindow"
xmlns:rxui="http://reactiveui.net"
Title="設備監控">
<StackPanel Margin="16">
<TextBox Text="{Binding DeviceIp, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="連線" Command="{Binding ConnectCommand}" />
<TextBlock Text="{Binding ConnectionStatus}" />
</StackPanel>
</Window>
本指南結構
| 頁面 | 內容 |
|---|---|
| 概觀(本頁) | ReactiveUI 定位、與其他框架比較、安裝 |
| 核心概念詳解 | ReactiveObject、WhenAnyValue、ReactiveCommand、OAPH、WhenActivated |
| 公司場景 Pattern | Dashboard、Recipe Editor、設備狀態管理、搜尋、Polly/FlaUI 整合 |
| 遷移指南 | 從 CommunityToolkit.Mvvm 漸進式遷移 |