← Frontend / Flutter

08_状态管理

Flutter 状态管理方案介绍

Flutter 状态管理方案对比与实践,从 setState 到 Provider、Riverpod。


方案选型

方案 对应 Web 适用场景 学习成本
setState useState / data() 单组件内部状态 ⭐
状态提升 props + emit 父子组件共享 ⭐
Provider Context API / Pinia 中大型 App,官方推荐 ⭐⭐
Riverpod Zustand / Jotai 大型 App,类型安全 ⭐⭐⭐
GetX 全家桶 快速开发 ⭐⭐
BLoC Redux 大型团队,强约束 ⭐⭐⭐⭐

setState(局部状态)

适合单组件内,不需要跨组件共享的状态:

class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int _count = 0;

  void _increment() {
    setState(() { _count++; }); // 包裹修改,触发重建
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text('$_count', style: const TextStyle(fontSize: 48)),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _increment,
        child: const Icon(Icons.add),
      ),
    );
  }
}

setState 常见坑:

// ❌ async 后 widget 可能已卸载
Future<void> loadData() async {
  final data = await fetchApi();
  setState(() { _data = data; }); // 可能报错
}

// ✅ 先检查 mounted
Future<void> loadData() async {
  final data = await fetchApi();
  if (mounted) setState(() { _data = data; });
}

状态提升(父子组件共享)

将状态提升到最近的公共父组件,类似 Vue 的 props + emit:

// 父组件持有状态
class _ParentState extends State<ParentWidget> {
  bool _isOn = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ToggleSwitch(
          value: _isOn,
          onChanged: (v) => setState(() => _isOn = v), // 回调修改父状态
        ),
        Text(_isOn ? '开启' : '关闭'),
      ],
    );
  }
}

// 子组件通过参数接收(类似 Vue props + emit)
class ToggleSwitch extends StatelessWidget {
  final bool value;
  final ValueChanged<bool> onChanged;  // 回调函数类型

  const ToggleSwitch({super.key, required this.value, required this.onChanged});

  @override
  Widget build(BuildContext context) {
    return Switch(value: value, onChanged: onChanged);
  }
}

Provider(全局状态,官方推荐)

# pubspec.yaml
dependencies:
  provider: ^6.1.0

第一步:定义数据模型

// 类似 Pinia 的 defineStore
class CartModel extends ChangeNotifier {
  final List<String> _items = [];

  List<String> get items => List.unmodifiable(_items);
  int get count => _items.length;

  void add(String item) {
    _items.add(item);
    notifyListeners(); // 通知所有监听者重建(类似 Vue 响应式触发)
  }

  void remove(String item) {
    _items.remove(item);
    notifyListeners();
  }

  void clear() {
    _items.clear();
    notifyListeners();
  }
}

第二步:注入 Provider(类似 Vue app.use(pinia))

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => CartModel()),
        ChangeNotifierProvider(create: (_) => UserModel()),
      ],
      child: const MyApp(),
    ),
  );
}

第三步:在组件中消费

class CartPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // context.watch:订阅变化,CartModel 更新时组件重建
    final cart = context.watch<CartModel>();

    return Column(
      children: [
        Text('购物车 (${cart.count} 件)'),
        Expanded(
          child: ListView.builder(
            itemCount: cart.items.length,
            itemBuilder: (ctx, i) => ListTile(
              title: Text(cart.items[i]),
              trailing: IconButton(
                icon: const Icon(Icons.delete),
                // context.read:仅读取,不订阅(用于事件处理)
                onPressed: () => context.read<CartModel>().remove(cart.items[i]),
              ),
            ),
          ),
        ),
        ElevatedButton(
          onPressed: () => context.read<CartModel>().clear(),
          child: const Text('清空'),
        ),
      ],
    );
  }
}

Provider 方法速查:

方法 场景 对比 Vue/React
context.watch<T>() build 中读取并订阅 computed / useStore()
context.read<T>() 事件处理中读取(不订阅) store.action()
context.select<T,R>((t) => t.x) 只订阅部分字段,减少重建 computed(() => store.x)
Consumer<T> 精细控制重建范围 局部 <template>

Consumer 精细控制重建范围

// ❌ 整个 build 因 CartModel 变化而重建(AppBar 也被重建了)
Widget build(BuildContext context) {
  final cart = context.watch<CartModel>();
  return Scaffold(
    appBar: AppBar(title: const Text('商店')), // 不需要 cart,但也重建了
    body: CartList(cart: cart),
  );
}

// ✅ 只让需要更新的部分重建
Widget build(BuildContext context) {
  return Scaffold(
    appBar: const AppBar(title: Text('商店')), // const,永不重建
    body: Consumer<CartModel>(
      builder: (ctx, cart, child) => CartList(cart: cart),
    ),
  );
}

Riverpod(进阶方案)

dependencies:
  flutter_riverpod: ^2.4.0
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 定义 Provider(全局,不在 Widget 内)
final counterProvider = StateNotifierProvider<CounterNotifier, int>(
  (ref) => CounterNotifier(),
);

class CounterNotifier extends StateNotifier<int> {
  CounterNotifier() : super(0);
  void increment() => state++;
  void decrement() => state--;
}

// 根组件包裹 ProviderScope
void main() {
  runApp(const ProviderScope(child: MyApp()));
}

// 组件继承 ConsumerWidget
class CounterPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider); // 订阅

    return Column(
      children: [
        Text('$count'),
        ElevatedButton(
          onPressed: () => ref.read(counterProvider.notifier).increment(),
          child: const Text('+1'),
        ),
      ],
    );
  }
}

Riverpod vs Provider:

Provider Riverpod
类型安全 一般 ✅ 编译期检查
无需 BuildContext ❌ ✅
测试友好 一般 ✅ 方便 mock
推荐场景 中型项目 大型项目