保持 Widget.build 内部的纯净

325 阅读2分钟

保持 Widget.build 内部的纯净

1762241737717.png

然而,众多状态管理库,以及像狗皮膏药一样的 flutter_hooks 全部违法这一原则。在 build 里为所欲为, 到处拉屎。

Provider 家族的隐式依赖

使用 Provider 时,开发者常在  build  方法中直接调用  Provider.of  或  watch :

  
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  嵌入状态逻辑:

  
Widget build(BuildContext context) {
  return BlocBuilder<CounterBloc, int>(
    builder: (context, state) { // 状态处理逻辑混杂在 UI 构建中
      return Text('$state');
    },
  );
}

 BlocBuilder  作为 UI 组件的一部分,让状态管理的实现细节暴露在  build  方法里,组件无法脱离 Bloc 框架独立复用。

Riverpod 的即时订阅

Riverpod 的  ref.watch  虽简化了订阅,但仍需在  build  中显式声明依赖:

  
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  方法仅通过简单属性获取数据,彻底剥离状态管理细节:

// 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 应有的清晰边界。