保持 Widget.build 内部的纯净
Provider 家族的隐式依赖
使用 Provider 时,开发者常在 build 方法中直接调用 Provider.of 或 watch :
dart
Widget build(BuildContext context) {
final user = Provider.of<User>(context); // 状态逻辑直接嵌入 UI
final theme = context.watch<ThemeProvider>();
return Text(user.name, style: TextStyle(color: theme.textColor));
}
这里的 build 方法不仅要描述 UI,还要处理状态订阅,一旦更换状态管理方式,所有 Provider.of 调用都需逐个修改,耦合度极高。
Bloc 的层级嵌套
Bloc 虽强调事件流分离,但在 UI 层仍需通过 BlocBuilder 嵌入状态逻辑:
dart
Widget build(BuildContext context) {
return BlocBuilder<CounterBloc, int>(
builder: (context, state) { // 状态处理逻辑混杂在 UI 构建中
return Text('$state');
},
);
}
BlocBuilder 作为 UI 组件的一部分,让状态管理的实现细节暴露在 build 方法里,组件无法脱离 Bloc 框架独立复用。
Riverpod 的即时订阅
Riverpod 的 ref.watch 虽简化了订阅,但仍需在 build 中显式声明依赖:
dart
Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.watch(counterProvider); // 状态订阅与 UI 强绑定
return Text('$counter');
}
build 方法被迫知晓 counterProvider 的存在,一旦 provider 定义变更,所有 build 的引用处都要调整,违背了"UI 只关心数据,不关心数据来源"的原则。
为什么不应该在 build 中耦合状态管理?
-
破坏单一职责: build 方法的核心是描述 UI 结构,混入状态订阅后,既要处理渲染逻辑,又要管理状态依赖,导致代码混乱。
-
阻碍组件复用:耦合了特定状态管理逻辑的 UI 组件,无法在其他框架或场景中直接复用,降低了代码的灵活性。
-
增加维护成本:当状态管理方案更换时,所有 build 方法中的订阅代码都需重构,牵一发而动全身。
替代方案
我在使用上述流行库后,发现他们的设计弊端,于是自己重新编写了一个简洁版的状态管理,没错状态管理就是一个很简单的东西,就像 Android 的 ViewModel 一样。本质上就是一个跟随生命周期的 vm 实例管理。但是不理解 flutter 这边的开发整的如此花里胡哨。
一种推荐:用 pub.dev/packages/vi... 解耦状态与 UI。
build 方法仅通过简单属性获取数据,彻底剥离状态管理细节:
dart
// ViewModel 层封装状态逻辑
class UserViewModel extends ViewModel{
final User user = User(name: 'Alice');
// 状态更新逻辑完全封装在此
}
class UserPageState extends State<UserPage> with ViewModelStateMixin{
late final UserViewModel _viewModel;
void initState(){
_viewModel = watchViewModel<UserViewModel>(factory: DefaultViewModelFactory(build:UserViewModel.new));
}
/// UI 层仅关注展示. 只持有 _viewModel。后续如何想替换底层的状态管理,
/// 修改 _viewModel 的实现即可。
Widget build(BuildContext context) {
return Text(_viewModel.user.name); // 无任何状态订阅代码
}
}
总结
让 UI 组件回归纯粹的展示职责,状态管理逻辑集中在 ViewModel ,不仅提升了代码可读性,还让组件复用和框架迁移变得轻松。当需要更换状态管理方式时,只需修改 ViewModel 内部实现, build 方法无需变动------这才是状态与 UI 应有的清晰边界。