Flutter状态管理总结

229 阅读3分钟

什么是状态管理

在响应式编程框架中, UI开发就是对状态(数据源)进行封装, 将之转换成具体的UI界面

简化为如下公式

image.png

  • UI: 表示屏幕上的展示的界面
  • f: 你实现的build方法
  • state: 状态(数据源)

Flutter通过build方法,将我们拥有的状态(数据源)转换成具体的UI界面

状态类型

  • 短时状态(ephemeral State): 包含在单个Widget且不会与其他Widget共享的状态
  • 应用状态(App State): 多个Widget共享的状态(数据)

image.png

共享类型

  • 子widget共享父类widget
  • 父widget共享子widget状态
  • 兄弟widget共享状态

如果widget之间需要共享状态,就把这个状态提升到两个Widget的共同祖先中,将这个短时状态变成应用状态

Flutter中状态管理要解决的根本问题,就是如何在任意一个Widget中获取应用状态

如何共享状态

  • 构造方法: 将父Widget中状态(数据)传递给子Widget
  • 提高父Widget层级, 比如把状态定义为全局的单例对象, 在任何地方都能获取到它

但这两种方式无法满足大型项目的需求, 大型项目一般使用如下三种办法, 最推荐的是使用 Provider

  • 事件同步(EventBus)
  • InheritedWidget
  • ProviderScoped Model 是对InheritedWidget的API封装,让我们能够少写重复性的代码

事件同步状态

  • 使用全局事件总线EventBus
  • 观察者模式的实现
  • 注意需要在 dispose时候 bus.off 关闭事件,避免内存泄漏
// 定义事件
enum Event{
  login,
  ...
}

// 登录状态改变后发布状态改变事件
bus.emit(Event.login);

void onLoginChanged(e){
}

@override
void initState() {
  //订阅登录状态改变事件
  bus.on(Event.login,onLogin);
  super.initState();
}

@override
void dispose() {
  //取消订阅
  bus.off(Event.login,onLogin);
  super.dispose();
}

InheritedWidget

  • 在 widget 树中从上到下``跨级共享数据,比如根 widget 中通过InheritedWidget共享了一个数据,那么便可以在任意子widget 中来获取该共享的数据!
  • 这个特性在一些需要在整个 widget 树中共享数据的场景中非常方便!如Flutter SDK中正是通过 InheritedWidget 来共享应用主题(Theme)和 Locale (当前语言环境)信息的。
class FrogColor extends InheritedWidget {
  /// 构造方法
  const FrogColor({
    super.key,
    required this.color,
    required super.child,
  });
  
  /// 需要共享的数据
  final Color color;

  /// 默认的约定:如果状态是希望暴露出的,应当提供一个`of`静态方法来获取其对象,开发者便可直接通过该方法来获取
  /// 返回实例对象,方便子树中的widget获取共享数据
  static FrogColor of(BuildContext context) {
    final FrogColor? result = maybeOf(context);
    assert(result != null, 'No FrogColor found in context');
    return result!;
  }

  /// 是否通知widget树中依赖该共享数据的子widget
  /// 这里当color发生变化时,是否通知子树中所有依赖color的Widget重新build
  /// 这里判断注意:是值改变还是内存地址改变。
  @override
  bool updateShouldNotify(FrogColor oldWidget) => color != oldWidget.color;
}

在子widget中调用of方法获取状态

// continuing from previous example...
class MyPage extends StatelessWidget {
  const MyPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FrogColor(
        color: Colors.green,
        child: Builder(
          builder: (BuildContext innerContext) {
            return Text(
              'Hello Frog',
              style: TextStyle(color: FrogColor.of(innerContext).color),
            );
          },
        ),
      ),
    );
  }
}

Provider

InheritedWidget的包装, 简化了状态共享和获取的代码,最推荐的状态管理方式

void main() {
  runApp(
    // MultiProvider 支持多个状态
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => Counter()),
      ],
      child: const MyApp(),
    ),
  );
}

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    // 通知所有依赖 Counter 的Widget刷新
    notifyListeners();
  }
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Example'),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.center,
          children: const <Widget>[
            Text('You have pushed the button this many times:'),

            /// 作为单独的小部件提取,用于性能优化。
            ///作为一个单独的小部件,它将独立于[MyHomePage]进行重建。
            ///同样,我们也可以使用[Consumer]或[Selector]。
            Count(),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        key: const Key('increment_floatingActionButton'),

        /// context.read读取状态, Counter变化时不会自动rebuild
        onPressed: () => context.read<Counter>().increment(),
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

class Count extends StatelessWidget {
  const Count({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text(
      /// watch 表示Counter变化时候自动rebuild widget
      '${context.watch<Counter>().count}',
      key: const Key('counterState'),
      style: Theme.of(context).textTheme.headlineMedium,
    );
  }
}

image.png

Model 数据改变, 通过 ChangeNotifierProvider 更新所有依赖改 Model 的 Widget