Flutter的状态管理工具

一、Provider

1.原理

Provider 本质上是基于 Flutter 的InheritedWidget 实现的,核心思想是数据自上而下传递,形成一个「数据提供者 - 消费者」的树形结构。

2、使用示例

2.1 定义可监听的状态模型(继承 ChangeNotifier) 核心:数据变化时调用 notifyListeners() 通知组件刷新

scala 复制代码
class LoginStatusModel extends ChangeNotifier {
  bool _isLogin = false;

  bool get isLogin => _isLogin;

  void updateLoginStatus(bool isLogin) {
    _isLogin = isLogin;
    notifyListeners();  // 关键:通知所有订阅的组件刷新
  }
}

2.2 使用 Provider或其子类 ,包裹 App实例,并将 状态模型实例作为值传递

csharp 复制代码
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => LoginStatusModel(), 
      child: const MyApp()、
    ),
  );
}

2.3 使用状态数据,在需要监听数据变化的Widget中,使用Provider.of、Consumer获取数据:

class 复制代码
  @override
  Widget build(BuildContext context) {
    // 使用 Consumer 监听 CounterModel
    return Consumer<LoginStatusModel>(
      builder: (context, loginStatus, child) {
        return Text('${loginStatus.isLogin}');
      },
    );
  }
}

// 也可以使用  Provider.of() 来获取:
Text('${Provider.of<LoginStatusModel>(context, listen: false).isLogin}')
特性 Provider.of<T>(context) Consumer<T> context.watch<T>() (推荐)
主要用途 灵活获取,常用于非 build 方法中 build 中获取并直接构建子 Widget build 中获取数据用于逻辑判断或属性赋值
是否监听变化 取决于 listen 参数 (默认 true) 是 (自动监听) 是 (自动监听)
代码位置 任意位置 (build 内/外,异步方法中) 只能在 build 方法的 return 树中 只能在 build 方法体内 (return 之前)
是否需要 builder 不需要 需要 (builder 回调) 不需要
典型场景 按钮点击事件、定时器、初始化逻辑 需要根据数据动态生成整个 Widget 时 需要根据数据决定 Widget 的属性 (颜色、文本) 时
性能优化 可设置 listen: false 避免不必要重绘 仅重建 Consumer 及其子节点 重建当前 Widget

A. Provider.of<T>(context)

这是最原始的方法。它的关键在于第二个参数 listen

  • listen: true (默认)

    • 行为:监听数据变化。如果数据变了,当前 Widget 会重建
    • 限制 :只能在 build 方法中使用(因为重建需要触发 build)。
    • 缺点 :如果在 build 中用默认值,会导致整个父 Widget 重绘,不够精细。
  • listen: false (常用)

    • 行为:不监听数据变化。只获取当前的实例对象。
    • 场景 :在事件回调 (如 onPressed)、initState、或者异步方法中调用修改数据的方法(如 increment())。
    • 优势:不会因为数据变化导致当前 Widget 无谓重绘。
@override 复制代码
Widget build(BuildContext context) {
  // ✅ 获取数据 (自动监听)
  final counter = context.watch<CounterModel>(); 
  
  // 可以在这里做逻辑处理
  final color = counter.count > 10 ? Colors.red : Colors.green;
  final text = counter.count > 10 ? '太多了!' : '正常';

  return Column(
    children: [
      // 使用处理后的数据
      Text(text, style: TextStyle(color: color)),
      
      // 按钮事件 (必须用 read 或 Provider.of(..., listen: false))
      ElevatedButton(
        onPressed: () => context.read<CounterModel>().increment(),
        child: Text('增加'),
      )
    ],
  );
}

B. Consumer 是一个 Widget。它的作用是将"获取数据"和"构建 UI"合二为一。

  • 特点 :它提供了一个 builder 函数。只有当数据变化时,只有这个 Consumer 节点及其子节点会重绘,它的父兄弟节点不会重绘。
  • 场景:当你需要根据数据直接返回一个新的 Widget 结构时。

// ✅ 场景:只想让这段文字区域刷新,不影响周围的布局

less 复制代码
Consumer<CounterModel>(
  builder: (context, counter, child) {
    // counter 就是 CounterModel 实例
    return Text(
      '当前计数: ${counter.count}',
      style: TextStyle(fontSize: 24, color: Colors.blue),
    );
  },
  // child 参数可用于优化:传递不变的子组件,避免每次重绘都重建它
  // child: Icon(Icons.star), 
)

C. context.watch<T>() ------ Consumer 的语法糖 (现代推荐)

这是 provider 6.0+ 版本后最推荐的写法。它等价于 Provider.of<T>(context, listen: true),但写法更简洁。

  • 特点 :直接在 build 方法体中使用,返回数据对象。
  • 场景 :当你需要在 build 方法中获取数据,用来计算属性、做条件判断,或者组合多个数据源时。
  • 注意 :调用 watch 的代码所在的 整个 Widget 的 build 方法 会在数据变化时重跑。如果该 Widget 很大,可能不如 Consumer 精准。
@override 复制代码
Widget build(BuildContext context) {
  // ✅ 获取数据 (自动监听)
  final counter = context.watch<CounterModel>(); 
  
  // 可以在这里做逻辑处理
  final color = counter.count > 10 ? Colors.red : Colors.green;
  final text = counter.count > 10 ? '太多了!' : '正常';

  return Column(
    children: [
      // 使用处理后的数据
      Text(text, style: TextStyle(color: color)),
      
      // 按钮事件 (必须用 read 或 Provider.of(..., listen: false))
      ElevatedButton(
        onPressed: () => context.read<CounterModel>().increment(),
        child: Text('增加'),
      )
    ],
  );
}

总结使用口诀

  • 改数据 (按钮/事件) ➡️ 用 read (或 of(..., listen: false))
  • 显数据 (局部刷新) ➡️ 用 Consumer
  • 显数据 (简单逻辑) ➡️ 用 watch
  • 初始化 (生命周期) ➡️ 用 of(..., listen: false)

二、RiverPod

1.原理

  • 将「状态」从 Widget 树中抽离,变成全局可访问的独立实体
  • 用「中心化的容器(ProviderContainer)」管理所有状态,Widget 仅作为「观察者」订阅状态,而非「依赖者」。

2、使用示例

  • 必须使用 ProviderScope 包裹整个应用。
import 复制代码
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(
    const ProviderScope( // 👈 必须包裹这里
      child: MyApp(),
    ),
  );
}
2.1 简单状态:@riverpod (替代 StateProvider)
// 复制代码
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 使用 @riverpod 注解,运行 build_runner 后会自动生成 CounterProvider
@riverpod
class Counter extends _$Counter {
  @override
  int build() {
    // 初始值
    return 0;
  }

  // 定义修改状态的方法
  void increment() {
    state++; // 👈 直接修改 state 属性,自动通知监听者
  }

  void reset() {
    state = 0;
  }
}
2.2 复杂状态:AsyncNotifier (替代 FutureProvider + StateNotifier)

用于处理异步操作(如网络请求)并管理复杂状态。这是 Riverpod 最强大的部分。

// 复制代码
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 模拟用户模型
class User {
  final String name;
  final int age;
  User({required this.name, required this.age});
}

// 定义 AsyncNotifier
@riverpod
class CurrentUser extends _$CurrentUser {
  @override
  Future<User> build() async {
    // 模拟网络延迟
    await Future.delayed(const Duration(seconds: 2));
    
    // 模拟可能发生的错误
    // if (someCondition) throw Exception("Failed to load");

    return User(name: "Alice", age: 25);
  }

  // 修改用户信息的方法
  Future<void> updateAge(int newAge) async {
    state = const AsyncValue.loading(); // 手动设置加载状态
    
    try {
      await Future.delayed(const Duration(seconds: 1)); // 模拟 API 调用
      final user = state.value!; // 获取旧数据
      state = AsyncValue.data(User(name: user.name, age: newAge)); // 更新数据
    } catch (e, st) {
      state = AsyncValue.error(e, st); // 处理错误
    }
  }
}
2.3 组合状态:派生数据 (Derived State)

在一个 Provider 中读取另一个 Provider,实现数据联动。

// 复制代码
@riverpod
String userNameRef(UserNameRef ref) {
  // 监听 CurrentUser Provider
  final userAsync = ref.watch(currentUserProvider);

  // 处理异步状态
  return userAsync.when(
    data: (user) => user.name,
    loading: () => "加载中...",
    error: (_, __) => "加载失败",
  );
}
A. 使用 ConsumerWidget (推荐)
less 复制代码
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 继承 ConsumerWidget
class HomePage extends ConsumerWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 1. 监听简单状态 (Counter)
    // ref.watch 会自动订阅,数据变化时重建此 Widget
    final count = ref.watch(counterProvider); 

    // 2. 监听异步状态 (CurrentUser)
    final userAsync = ref.watch(currentUserProvider);

    return Scaffold(
      appBar: AppBar(title: const Text("Riverpod Demo")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 显示异步用户数据
            userAsync.when(
              data: (user) => Text("你好, ${user.name} (年龄: ${user.age})"),
              loading: () => const CircularProgressIndicator(),
              error: (err, stack) => Text("错误: $err"),
            ),
            
            const SizedBox(height: 20),

            // 显示计数
            Text("计数: $count", style: const TextStyle(fontSize: 24)),
            
            const SizedBox(height: 20),

            // 3. 修改状态 (使用 ref.read 或 ref.notifier)
            ElevatedButton(
              onPressed: () {
                // 方式 A: 直接调用生成的 notifier 方法 (推荐)
                ref.read(counterProvider.notifier).increment();
                
                // 方式 B: 如果是 AsyncNotifier
                // ref.read(currentUserProvider.notifier).updateAge(26);
              },
              child: const Text("增加计数"),
            ),
            
            ElevatedButton(
              onPressed: () {
                // 触发异步更新
                ref.read(currentUserProvider.notifier).updateAge(30);
              },
              child: const Text("更新用户年龄 (异步)"),
            ),
          ],
        ),
      ),
    );
  }
}
B. 在非 Widget 类中使用 (Riverpod 的杀手锏)

由于不依赖 Context,你可以在任何地方(如路由守卫、服务类、甚至 main 函数之后)访问状态。

csharp 复制代码
// 例如:在一个普通的 Dart 类中
class AnalyticsService {
  final Ref ref; // 注入 Ref

  AnalyticsService(this.ref);

  void logCount() {
    // 直接读取当前值,不订阅变化 (类似 listen: false)
    final currentCount = ref.read(counterProvider);
    print("当前计数是: $currentCount");
  }
  
  void subscribeToCount() {
    // 也可以手动监听变化
    ref.listen(counterProvider, (previous, next) {
      print("计数从 $previous 变成了 $next");
    });
  }
}
特性 Provider (旧) Riverpod (新)
依赖 Context ✅ 强依赖 (BuildContext) ❌ 无依赖 (WidgetRefRef)
类型安全 ⚠️ 运行时检查 (容易崩溃) ✅ 编译时检查 (配合代码生成)
异步支持 🆗 需要 FutureProvider 🚀 原生强大 (AsyncValue, when)
状态组合 😐 较难,容易嵌套地狱 🤩 极简 (ref.watch 其他 Provider)
测试难度 😫 需要 Mock Context 😃 极易 (直接创建 ProviderContainer)
代码量 多 (样板代码) 少 (配合 @riverpod 宏)
学习曲线 低 (但精通难) 中 (概念多,但逻辑清晰)
相关推荐
前端Hardy2 小时前
Flutter vs React Native vs HarmonyOS:谁更适合下一代跨端?2026 年技术选型终极指南
前端·flutter·react native
不爱吃糖的程序媛3 小时前
鸿蒙 Flutter 多引擎场景开发指导
flutter·华为·harmonyos
ITKEY_3 小时前
flutter 在iPad mini7上真机运行实战(踩坑)
flutter·ios·ipad
恋猫de小郭3 小时前
Android 性能迎来提升:内核引入 AutoFDO 普惠所有 15-16 设备
android·前端·flutter
Justin在掘金3 小时前
Flutter Engine、Dart VM、Runner、鸿蒙端进程与线程 —— 深度解析
flutter
程序员Ctrl喵7 小时前
渲染流水线:从代码到像素的“非凡旅程”
flutter
王码码20359 小时前
Flutter for OpenHarmony:es_compression — 高性能 Brotli 与 Zstd 算法实战
算法·flutter·elasticsearch
左手厨刀右手茼蒿9 小时前
Flutter 三方库 build_modules 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、模块化的 Dart 代码编译策略与构建流水线系统
flutter·harmonyos·鸿蒙·openharmony·build_modules
鹏多多.19 小时前
Flutter使用screenshot进行截屏和截长图以及分享保存的全流程指南
android·前端·flutter·ios·前端框架