You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
7.6 KiB
7.6 KiB
ReactiveUI 架构检查报告
📋 检查日期
2025年1月(当前时间)
✅ 符合 ReactiveUI 最佳实践的项目
1. ViewModels 继承结构 ✅
- MainWindowViewModel: 正确继承
ReactiveObject✅ - RoutableViewModel: 正确继承
ReactiveObject并实现IRoutableViewModel✅ - 所有页面 ViewModel: 正确继承
RoutableViewModel✅- DashboardPageViewModel ✅
- UsersPageViewModel ✅
- SettingsPageViewModel ✅
- ReportsPageViewModel ✅
- HelpPageViewModel ✅
- DialogHostPageViewModel ✅
- IconsPageViewModel ✅
- EditorPageViewModel ✅
2. Views 继承结构 ✅
- MainWindow: 正确继承
ReactiveWindow<AppViewModel>✅ - 所有页面 View: 正确继承
ReactiveUserControl<ViewModel>✅- DashboardPageView ✅
- UsersPageView ✅
- SettingsPageView ✅
- ReportsPageView ✅
- HelpPageView ✅
- DialogHostPageView ✅
- IconsPageView ✅
- EditorPageView ✅
3. 响应式属性通知 ✅
所有 ViewModels 都正确使用 RaiseAndSetIfChanged 进行属性变更通知:
public string Title
{
get => _title;
set => this.RaiseAndSetIfChanged(ref _title, value);
}
4. IScreen 和 RoutingState 使用 ✅
- AppViewModel: 正确实现
IScreen接口,提供RoutingState✅ - MainWindowViewModel: 正确使用
IScreen进行路由导航 ✅ - 路由导航: 使用
Router.Navigate.Execute(viewModel)进行导航 ✅
5. ReactiveCommand 使用 ✅
- MainWindowViewModel: 正确使用
ReactiveCommand创建命令 ✅- NavigateCommand ✅
- CloseTabCommand ✅
- SelectTabCommand ✅
- 其他 ViewModels: 也正确使用 ReactiveCommand ✅
6. 响应式编程模式 ✅
正确使用 WhenAnyValue 监听属性变化:
this.WhenAnyValue(x => x.SelectedNavigationItem)
.Where(item => item != null)
.Subscribe(item => ...);
7. ViewLocator 实现 ✅
- 自定义 ViewLocator: 正确实现
IViewLocator接口 ✅ - 命名约定: 遵循 ViewModel/View 命名约定 ✅
- 注册: 在 App 初始化时正确注册到 Splat ✅
8. 路由系统集成 ✅
- RoutedViewHost: MainWindow.axaml 正确使用
<reactive:RoutedViewHost Router="{Binding Router}"/>✅ - 路由状态监听: 正确监听
Router.CurrentViewModel变化 ✅
9. 依赖注入配置 ✅
- ReactiveUI 服务注册: 正确注册 IScreen 和 ViewModel ✅
- 服务生命周期: 使用适当的生命周期(Singleton/Transient) ✅
10. XAML 绑定 ✅
- ReactiveWindow/ReactiveUserControl: 所有 XAML 文件正确使用 reactive 命名空间 ✅
- 绑定语法: 使用标准的 Avalonia 绑定语法 ✅
- 命令绑定: 正确绑定 ReactiveCommand ✅
⚠️ 潜在问题和建议
1. MainWindow ViewModel 设置不一致 ⚠️
问题描述:
// MainWindow.axaml.cs
public partial class MainWindow : ReactiveWindow<AppViewModel>
{
public MainWindow(AppViewModel appViewModel, ...)
{
ViewModel = appViewModel; // ✅ 正确
DataContext = appViewModel.MainWindowViewModel; // ⚠️ 这里设置了 DataContext
}
}
分析:
ReactiveWindow<AppViewModel>的ViewModel属性应该是AppViewModel✅- 但是
DataContext被设置为MainWindowViewModel - 在 XAML 中,绑定会使用
DataContext(MainWindowViewModel),这可以正常工作 - 但 ReactiveUI 的最佳实践是:如果使用
ReactiveWindow<AppViewModel>,应该通过ViewModel.MainWindowViewModel.Router来访问 Router
当前绑定:
<reactive:RoutedViewHost Router="{Binding Router}"/>
这个绑定会从 DataContext(MainWindowViewModel)获取 Router,可以正常工作。
建议:
-
选项 1(推荐): 如果 MainWindow 的主要功能都在 MainWindowViewModel 中,建议改为:
public partial class MainWindow : ReactiveWindow<MainWindowViewModel> { public MainWindow(MainWindowViewModel viewModel, ...) { ViewModel = viewModel; // 不需要单独设置 DataContext } }这样更符合 ReactiveUI 的设计模式。
-
选项 2: 保持当前结构,但明确绑定路径:
<reactive:RoutedViewHost Router="{Binding ViewModel.MainWindowViewModel.Router}"/>然后将 MainWindow.axaml 的绑定改为从 ViewModel.MainWindowViewModel 获取。
2. ViewLocator 使用 Activator.CreateInstance ⚠️
当前实现:
var view = Activator.CreateInstance(viewType) as IViewFor;
问题:
- 没有使用依赖注入创建 View 实例
- 如果 View 的构造函数需要参数,会失败
建议: 如果项目使用依赖注入,可以考虑从 DI 容器获取 View 实例:
// 从 ServiceProvider 获取 View(如果已注册)
var serviceProvider = Locator.Current.GetService<IServiceProvider>();
if (serviceProvider != null)
{
view = serviceProvider.GetService(viewType) as IViewFor;
}
// 如果 DI 中没有,再使用 Activator
if (view == null)
{
view = Activator.CreateInstance(viewType) as IViewFor;
}
3. ReactiveCommand 的异步支持 💡
当前实现:
NavigateCommand = ReactiveCommand.Create<NavigationItem>(NavigateToPage);
建议: 如果命令需要异步操作或验证,可以使用:
NavigateCommand = ReactiveCommand.CreateFromTask<NavigationItem>(async item =>
{
await NavigateToPageAsync(item);
});
4. 订阅的清理 💡
当前实现:
_screen.Router
.CurrentViewModel
.Subscribe(viewModel => { ... });
建议:
使用 WhenActivated 确保订阅在视图激活时创建,在停用时清理:
this.WhenActivated(disposables =>
{
_screen.Router
.CurrentViewModel
.Subscribe(viewModel => { ... })
.DisposeWith(disposables);
});
但这需要在 View 中实现 IActivatableView 接口。
📊 总体评分
| 检查项 | 状态 | 评分 |
|---|---|---|
| ViewModels 继承结构 | ✅ | 10/10 |
| Views 继承结构 | ✅ | 10/10 |
| 响应式属性通知 | ✅ | 10/10 |
| IScreen/RoutingState | ✅ | 9/10 |
| ReactiveCommand | ✅ | 9/10 |
| 响应式编程模式 | ✅ | 8/10 |
| ViewLocator | ✅ | 8/10 |
| 路由系统集成 | ✅ | 9/10 |
| 依赖注入配置 | ✅ | 9/10 |
| XAML 绑定 | ✅ | 10/10 |
总体评分: 9.0/10 ⭐⭐⭐⭐⭐
🎯 结论
项目基本完全遵循 ReactiveUI 最佳实践,架构设计良好。主要符合点:
- ✅ 正确的继承结构(ReactiveObject, ReactiveUserControl, ReactiveWindow)
- ✅ 完整的路由系统集成(IScreen, RoutingState, RoutedViewHost)
- ✅ 响应式编程模式(RaiseAndSetIfChanged, ReactiveCommand, WhenAnyValue)
- ✅ 自定义 ViewLocator 实现
- ✅ 完整的依赖注入集成
主要建议:
- 考虑统一 MainWindow 的 ViewModel 设置(选项 1 更推荐)
- 改进 ViewLocator 以支持依赖注入
- 考虑使用
WhenActivated进行订阅管理
📝 最佳实践检查清单
- ViewModels 继承 ReactiveObject
- 路由 ViewModels 实现 IRoutableViewModel
- Views 继承 ReactiveUserControl 或 ReactiveWindow
- 使用 RaiseAndSetIfChanged 进行属性通知
- 使用 ReactiveCommand 创建命令
- 使用 IScreen 和 RoutingState 进行路由
- 使用 RoutedViewHost 显示路由内容
- 实现自定义 ViewLocator
- 正确注册 ReactiveUI 服务
- 使用响应式编程模式(WhenAnyValue 等)
- 使用 WhenActivated 管理订阅(可选)
- ViewLocator 支持依赖注入(可选)
检查完成日期: 2025年1月 检查工具: AI 代码分析