> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Flutter 状态管理
- URL: https://blog.vercanti.com/flutter-zhuang-tai-guan-li/
- Published: 2026-08-28T14:35:44.000Z
- Updated: 2026-08-28T14:59:30.000Z
- Description: 运行代码生成： AsyncValue 常用方法： 在 build 方法中错误地使用 ref.read 会导致状态变化后 UI 不更新： 小组件用 StatefulWidget，应用级状态用 Riverpod：setState 适合局部 UI 状态（按钮选中、展开收起），跨页面共享状态改用 Riverpod 的 Provider/AsyncNotifier，避免 prop drilling。 Riverpod 优先用 AsyncNotifierProvider：异步数据（API 调用）统一用 AsyncNotifierProvider，自动处理 loadi
- Author: yellowdog
- Tags: 移动开发

> 官方文档：<https://docs.flutter.dev/data-and-backend/state-mgmt/intro>  
> 适用版本：Flutter 3.x（2026-05-07 核实）

## 状态管理方案对比

| 方案       | 学习曲线 | 适用规模   | 测试友好度 | 备注               |
| -------- | ---- | ------ | ----- | ---------------- |
| setState | 低    | 小型/组件级 | 一般    | Flutter 内置，无需依赖  |
| Provider | 低-中  | 小型-中型  | 良好    | 官方推荐入门方案         |
| Riverpod | 中    | 中型-大型  | 优秀    | Provider 的改进版，推荐 |
| Bloc     | 中-高  | 中型-大型  | 优秀    | 事件驱动，结构严格        |
| GetX     | 低    | 中小型    | 较差    | 功能大而全，侵入性强       |

---

## Riverpod（推荐方案）

### 安装

```yaml
# pubspec.yaml
dependencies:
  flutter_riverpod: ^2.5.1
  riverpod_annotation: ^2.3.5

dev_dependencies:
  build_runner: ^2.4.9
  riverpod_generator: ^2.4.0

```

### Provider 类型

| Provider 类型           | 用途                   | 返回值特征                       |
| --------------------- | -------------------- | --------------------------- |
| Provider              | 只读依赖、常量计算值           | 不可变                         |
| StateProvider         | 简单状态（计数器、开关等）        | 可通过 ref.read().state = x 修改 |
| StateNotifierProvider | 复杂状态，Riverpod 1.x 推荐 | StateNotifier 子类            |
| FutureProvider        | 单次异步请求               | AsyncValue<T>               |
| StreamProvider        | 持续监听的数据流             | AsyncValue<T>               |
| NotifierProvider      | 复杂状态，Riverpod 2.x 推荐 | Notifier 子类                 |

### ref.watch / ref.read / ref.listen 区别

| 方法                             | 触发重建    | 使用场景                    |
| ------------------------------ | ------- | ----------------------- |
| ref.watch(provider)            | 是       | Widget build 方法内，监听状态变化 |
| ref.read(provider)             | 否       | 事件回调内，只需读取一次或调用方法       |
| ref.listen(provider, callback) | 否（手动处理） | 监听变化并执行副作用（导航、弹窗等）      |

### StateNotifier + StateNotifierProvider 示例

```dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 状态类
class CounterState {
  final int count;
  final bool isLoading;

  const CounterState({required this.count, this.isLoading = false});

  CounterState copyWith({int? count, bool? isLoading}) {
    return CounterState(
      count: count ?? this.count,
      isLoading: isLoading ?? this.isLoading,
    );
  }
}

// StateNotifier
class CounterNotifier extends StateNotifier<CounterState> {
  CounterNotifier() : super(const CounterState(count: 0));

  void increment() {
    state = state.copyWith(count: state.count + 1);
  }

  void decrement() {
    state = state.copyWith(count: state.count - 1);
  }

  Future<void> fetchFromServer() async {
    state = state.copyWith(isLoading: true);
    await Future.delayed(const Duration(seconds: 1));
    state = state.copyWith(count: 42, isLoading: false);
  }
}

// Provider 定义
final counterProvider =
    StateNotifierProvider<CounterNotifier, CounterState>((ref) {
  return CounterNotifier();
});

// 使用
class CounterWidget extends ConsumerWidget {
  const CounterWidget({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(counterProvider);
    return Column(
      children: [
        Text('Count: ${state.count}'),
        ElevatedButton(
          onPressed: () => ref.read(counterProvider.notifier).increment(),
          child: const Text('Increment'),
        ),
      ],
    );
  }
}

```

### Notifier + NotifierProvider（Riverpod 2.x 新 API）

```dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

class CounterNotifier2 extends Notifier<int> {
  @override
  int build() => 0; // 初始状态

  void increment() => state++;
  void decrement() => state--;
}

final counterProvider2 = NotifierProvider<CounterNotifier2, int>(() {
  return CounterNotifier2();
});

```

### 代码生成：@riverpod 注解

```dart
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'counter.g.dart';

@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;

  void increment() => state++;
}

// 异步 Provider
@riverpod
Future<List<String>> userList(UserListRef ref) async {
  final response = await http.get(Uri.parse('https://api.example.com/users'));
  return parseUsers(response.body);
}

```

运行代码生成：

```bash
dart run build_runner build --delete-conflicting-outputs

```

### AsyncValue 处理异步状态

```dart
@riverpod
Future<User> userDetail(UserDetailRef ref, int id) async {
  return await apiClient.getUser(id);
}

// 在 Widget 中使用
class UserDetailWidget extends ConsumerWidget {
  final int userId;
  const UserDetailWidget({super.key, required this.userId});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final asyncUser = ref.watch(userDetailProvider(userId));

    return asyncUser.when(
      data: (user) => Text(user.name),
      loading: () => const CircularProgressIndicator(),
      error: (error, stack) => Text('Error: $error'),
    );
  }
}

```

`AsyncValue` 常用方法：

| 方法/属性                         | 说明                        |
| ----------------------------- | ------------------------- |
| when(data:, loading:, error:) | 分支处理三种状态                  |
| maybeWhen(...)                | 仅处理部分分支，其余走 orElse        |
| whenData((data) => ...)       | 仅转换 data，保留 loading/error |
| value                         | 获取数据，可能为 null             |
| hasValue                      | 是否有数据（包括刷新中）              |
| isLoading                     | 是否正在加载                    |
| hasError                      | 是否有错误                     |

### ref.invalidate() 刷新 Provider

```dart
// 强制重新执行 FutureProvider 或 StreamProvider
ref.invalidate(userListProvider);

// 带参数的 Provider
ref.invalidate(userDetailProvider(userId));

```

### ProviderScope 覆盖（测试场景）

```dart
// 测试中覆盖 Provider
testWidgets('shows user name', (tester) async {
  await tester.pumpWidget(
    ProviderScope(
      overrides: [
        userDetailProvider(1).overrideWith((ref) async => User(name: 'Test')),
      ],
      child: const MaterialApp(home: UserDetailWidget(userId: 1)),
    ),
  );
  expect(find.text('Test'), findsOneWidget);
});

```

---

## Bloc

### Event / State / Bloc 三层结构

```dart
// Event
abstract class CounterEvent {}
class CounterIncremented extends CounterEvent {}
class CounterDecremented extends CounterEvent {}

// State
class CounterState {
  final int count;
  const CounterState(this.count);
}

// Bloc
class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(const CounterState(0)) {
    on<CounterIncremented>((event, emit) {
      emit(CounterState(state.count + 1));
    });
    on<CounterDecremented>((event, emit) {
      emit(CounterState(state.count - 1));
    });
  }
}

```

### BlocProvider / BlocBuilder / BlocListener / BlocConsumer

| Widget/方法    | 参数                                             | 说明                      |
| ------------ | ---------------------------------------------- | ----------------------- |
| BlocProvider | create, child, lazy                            | 创建并向下注入 Bloc            |
| BlocBuilder  | bloc, buildWhen, builder                       | 监听状态并重建 UI              |
| BlocListener | bloc, listenWhen, listener                     | 监听状态执行副作用，不重建           |
| BlocConsumer | bloc, buildWhen, listenWhen, builder, listener | 同时具备 Builder 和 Listener |

```dart
// 提供 Bloc
BlocProvider(
  create: (context) => CounterBloc(),
  child: const CounterView(),
)

// 重建 UI
BlocBuilder<CounterBloc, CounterState>(
  buildWhen: (previous, current) => previous.count != current.count,
  builder: (context, state) => Text('${state.count}'),
)

// 副作用（导航、弹窗）
BlocListener<CounterBloc, CounterState>(
  listenWhen: (previous, current) => current.count == 10,
  listener: (context, state) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('Reached 10!')),
    );
  },
  child: const CounterView(),
)

```

### Cubit（简化版 Bloc）

```dart
// 无 Event，直接调用方法修改状态
class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);

  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
}

```

### context.read vs context.watch

| 方法                      | 触发重建 | 使用场景            |
| ----------------------- | ---- | --------------- |
| context.read<MyBloc>()  | 否    | 事件回调中触发 Bloc 事件 |
| context.watch<MyBloc>() | 是    | build 方法中监听状态   |

```dart
// 事件回调中用 read
ElevatedButton(
  onPressed: () => context.read<CounterBloc>().add(CounterIncremented()),
  child: const Text('+'),
)

// build 中用 watch
Text('${context.watch<CounterBloc>().state.count}')

```

### Bloc 测试

```dart
import 'package:bloc_test/bloc_test.dart';

void main() {
  group('CounterBloc', () {
    late CounterBloc bloc;

    setUp(() => bloc = CounterBloc());
    tearDown(() => bloc.close());

    blocTest<CounterBloc, CounterState>(
      'emits [CounterState(1)] when CounterIncremented is added',
      build: () => CounterBloc(),
      act: (bloc) => bloc.add(CounterIncremented()),
      expect: () => [const CounterState(1)],
    );
  });
}

```

---

## 踩坑与注意事项

### Riverpod 中 ref.read 不触发重建

在 `build` 方法中错误地使用 `ref.read` 会导致状态变化后 UI 不更新：

```dart
// 错误：ref.read 不监听变化
Widget build(BuildContext context, WidgetRef ref) {
  final count = ref.read(counterProvider); // 只读一次，不更新
  return Text('$count');
}

// 正确：在 build 中使用 ref.watch
Widget build(BuildContext context, WidgetRef ref) {
  final count = ref.watch(counterProvider);
  return Text('$count');
}

```

### Riverpod 中 Provider 不可在 build 之外使用 ref.watch

```dart
// 错误：在回调中使用 ref.watch
ElevatedButton(
  onPressed: () {
    final value = ref.watch(someProvider); // 会抛出异常
  },
  child: const Text('OK'),
)

// 正确：回调中用 ref.read
ElevatedButton(
  onPressed: () {
    final value = ref.read(someProvider);
  },
  child: const Text('OK'),
)

```

### Bloc 中 emit 不可在 close 后调用

```dart
class MyBloc extends Bloc<MyEvent, MyState> {
  MyBloc() : super(MyInitial()) {
    on<LoadData>((event, emit) async {
      // 如果 Bloc 在请求返回前被关闭，emit 会抛出 StateError
      final data = await repository.fetchData();
      // 需要检查是否已关闭
      if (!isClosed) {
        emit(MyLoaded(data));
      }
    });
  }
}

```

### Riverpod ProviderScope 必须在 runApp 最外层

```dart
void main() {
  runApp(
    const ProviderScope( // 必须包裹整个应用
      child: MyApp(),
    ),
  );
}

```

---

## 最佳实践

**小组件用 `StatefulWidget`，应用级状态用 Riverpod**：`setState` 适合局部 UI 状态（按钮选中、展开收起），跨页面共享状态改用 Riverpod 的 `Provider`/`AsyncNotifier`，避免 prop drilling。

**Riverpod 优先用 `AsyncNotifierProvider`**：异步数据（API 调用）统一用 `AsyncNotifierProvider`，自动处理 loading/error/data 三态，配合 `ref.watch` 在 UI 层声明式消费：

```dart
@riverpod
class UserList extends _$UserList {
  @override
  Future<List<User>> build() => fetchUsers();

  Future<void> refresh() => ref.refresh(userListProvider.future);
}

```

**Bloc 场景下 Event → State 单向数据流**：所有状态变更通过 `add(Event)` 触发，禁止在 Bloc 外部直接修改 State，保持可测试性和可追踪性。

**避免在 `Provider` 构建函数中执行副作用**：`build()` 方法可能被多次调用，副作用（网络请求、写文件）应放在 `AsyncNotifier.build` 里（返回 Future），而不是构造函数。

**`ref.listen` 替代命令式导航**：在 Riverpod 中监听状态变化后触发导航，比 `StreamBuilder` 更简洁，避免在 `build` 里写命令式逻辑：

```dart
ref.listen(authProvider, (prev, next) {
  if (next.isLoggedOut) context.go('/login');
});

```

---

## 常见陷阱

### 陷阱：Riverpod `ref.watch` 在非 `build` 方法中调用

**现象：** 在事件处理函数或 `initState` 中调用 `ref.watch`，Provider 不更新或报断言错误。  
**原因：** `ref.watch` 只在 `build` 方法中有效，非构建期调用不会建立订阅关系。  
**解决：** 非 `build` 期读取用 `ref.read`，监听变化用 `ref.listen`。

### 陷阱：Bloc `emit` 相同 State 不触发重建

**现象：** `emit(state.copyWith(count: count))` 后 UI 没有更新。  
**原因：** Bloc 默认用 `==` 比较新旧 State，若引用或值相同则不会触发 rebuild。  
**解决：** 确保 State 实现 `Equatable` 且 `props` 包含所有可变字段，或每次 emit 新实例而非同一引用。

### 陷阱：全局 Provider 持有 BuildContext 导致内存泄漏

**现象：** 页面销毁后 Provider 仍存在，持有的 `context` 引用导致整棵 Widget 树无法 GC。  
**原因：** 在 Provider 的构造函数或 `build()` 中捕获了 `context`，而 Provider 生命周期比 Widget 长。  
**解决：** Provider 层禁止持有 `context`，UI 交互回调通过 `ref.read` 获取服务后执行操作。

---

## 参见

[Flutter入门](https://blog.vercanti.com/flutter-ru-men/)  
[GoRouter完全指南](https://blog.vercanti.com/gorouter-wan-quan-zhi-nan/)