你不需要那么多Provider——重新理解状态管理与业务逻辑

你不需要那么多Provider------重新理解状态管理与业务逻辑

状态管理一直是Flutter的热门话题, 而Provider/Riverpod更是Flutter官方的Favorite. 然而, 你真的需要这么多基于Widget树/BuildContext的状态管理吗?

本文将围绕Provider的核心机制, 重新审视状态与业务逻辑的本质, 并探讨全局状态管理的优势与潜在问题, 带你找到更优雅的状态管理方式.

一、Provider的核心: InheritedWidget与Widget树

Provider的核心在于利用Flutter的InheritedWidget机制, 通过Widget树实现数据的分发与访问. 它的关键优势是: 底层Widget可以向上查找, 找到Widget树中离自己最近的匹配数据. 这种机制非常适合需要根据Widget树位置动态获取数据的场景.

典型场景1: 主题样式

以主题样式为例, 假设我们在Widget树的顶层和中层分别放置了两个不同的ThemeData实例:

dart 复制代码
Widget build(BuildContext context) {
  return Provider<ThemeData>(
    create: (_) => ThemeData(primaryColor: Colors.blue),
    child: Column(
      children: [
        // 使用顶层主题(蓝色)
        MyWidget(),
        Provider<ThemeData>(
          create: (_) => ThemeData(primaryColor: Colors.red),
          child: MyWidget(), // 使用中层主题(红色)
        ),
      ],
    ),
  );
}

在这个例子中, 顶层以上的Widget会使用蓝色主题, 而中层以下的Widget会使用红色主题. 这种基于Widget树位置的数据查找机制, 让主题管理变得直观且高效.

典型场景2: 国际化

再来看一个更复杂的例子: 一个外语学习App, 用户交互页面需要使用用户的母语(例如中文), 而内部的教学页面需要使用教学语言(例如英文). 通过Provider, 我们可以在Widget树的不同层级放置不同的语言配置:

dart 复制代码
Widget build(BuildContext context) {
  return Provider<Locale>(
    create: (_) => Locale('zh', 'CN'), // 顶层: 中文
    child: Scaffold(
      body: Column(
        children: [
          // 使用中文
          UserInteractionWidget(),
          Provider<Locale>(
            create: (_) => Locale('en', 'US'), // 中层: 英文
            child: TeachingWidget(), // 使用英文
          ),
        ],
      ),
    ),
  );
}

通过这种方式, App可以根据Widget树的位置实现语言的自动切换, 非常适合需要动态区域化配置的场景.

二、效率的矛盾: 状态数据与Widget树无关

尽管Provider在主题和国际化等场景中表现出色, 但实际开发中需要管理的状态往往是用户操作数据, 例如表单输入、计数器数值等. 这些数据的特点是: 它们与Widget树的具体位置无关.

Counter应用的例子

以经典的Counter应用为例, 无论是将"+1"按钮放在AppBar中, 还是放在页面的Body内部, 其业务逻辑始终是: 点击按钮后, 计数器数值加1:

dart 复制代码
class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            icon: Icon(Icons.add),
            onPressed: () => context.read<Counter>().increment(),
          ),
        ],
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () => context.read<Counter>().increment(),
          child: Text('Add'),
        ),
      ),
    );
  }
}

无论按钮位于Widget树的哪个位置, 计数器的逻辑都不会改变. 这表明, 业务逻辑的核心是数据本身, 而不是数据的分发方式.

三、从局部到全局几乎是一种必然

显而易见的是: 随着业务需求变化, 业务组件("+1"按钮)会四处移动, 必然导致存储count数据的Provider<Counter>最终移动到Widget树的顶层. <Counter>从事实上变成了全局变量(尽管会有人试图在进入/离开特定页面时将其加载/释放)

既然所有的业务逻辑组件最终都会让状态Provider上移到App顶层, 那么为什么不一步到位, 从一开始就将状态放在全局变量中呢?

业务逻辑与UI组件分离

例如, 我们可以将计数器的状态放置在全局Map中

dart 复制代码
// 存储全局状态
final global_state_map = {};

class CounterPage extends StatelessWidget {
  @override
  void initState() {
    // 初始化全局状态
    global_state_map['count'] = 0;
    super.initState();
  }
  @override
  void dispose() {
    // 清理页面相关的全局状态
    global_state_map.remove('count');
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            icon: Icon(Icons.add),
            onPressed: () => global_state_map['count']+=1,
          ),
        ],
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () => global_state_map['count']+=1,
          child: Text('Add'),
        ),
      ),
    );
  }
}

当然, 移除Provider后又涉及到刷新后对UI的通知. 以上代码也仅仅是演示, 实际上可以通过存储Stream+StreamBuilder等方案来实现页面刷新. 在后续的文章中, 将会介绍完整的方案.

四、总结

Provider的InheritedWidget机制为主题、国际化等场景提供了优雅的解决方案, 但开发场景下, 业务逻辑相关的数据没有必要与Widget树耦合. 受限于篇幅, 具体的开发方案敬请期待后续文章.

心急的朋友可以直接查看 HiveState 仓库: github.com/Hu-Wentao/h... 示例与example均基本完备, 选择web浏览器设备即可运行web demo;

相关推荐
weifexie20 分钟前
ruby可变参数
开发语言·前端·ruby
千野竹之卫21 分钟前
3D珠宝渲染用什么软件比较好?渲染100邀请码1a12
开发语言·前端·javascript·3d·3dsmax
sunbyte21 分钟前
初识 Three.js:开启你的 Web 3D 世界 ✨
前端·javascript·3d
半兽先生42 分钟前
WebRtc 视频流卡顿黑屏解决方案
java·前端·webrtc
南星沐2 小时前
Spring Boot 常用依赖介绍
java·前端·spring boot
孙_华鹏2 小时前
手撸一个可以语音操作高德地图的AI智能体
前端·javascript·coze
zhangxingchao2 小时前
Jetpack Compose 动画
前端
@PHARAOH3 小时前
HOW - 缓存 React 自定义 hook 的所有返回值(包括函数)
前端·react.js·缓存
拉不动的猪3 小时前
设计模式之--------工厂模式
前端·javascript·架构
前端开发张小七3 小时前
16.Python递归详解:从原理到实战的完整指南
前端·python