go to index

Flutter 状态管理

read time 10 min read
Flutter 状态管理

状态提升

状态提升是 Flutter 中的一种状态管理方法,用于在多个组件之间共享状态。通过将状态提升到组件树的更高层次,可以更容易地管理和更新状态。

示例

假设我们有两个子组件,分别是 CounterCounterDisplay,它们都用于显示计数器的值。我们可以将计数器的状态提升到父组件 CounterApp 中,并通过这两个子组件来展示状态。

dart
class CounterApp extends StatefulWidget {
  @override
  _CounterAppState createState() => _CounterAppState();
}

class _CounterAppState extends State<CounterApp> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Counter App')),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Counter(counter: _counter),
            CounterDisplay(counter: _counter),
          ],
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: _incrementCounter,
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

class Counter extends StatelessWidget {
  final int counter;

  Counter({required this.counter});

  @override
  Widget build(BuildContext context) {
    return Text('Counter: $counter', style: TextStyle(fontSize: 24));
  }
}

class CounterDisplay extends StatelessWidget {
  final int counter;

  CounterDisplay({required this.counter});

  @override
  Widget build(BuildContext context) {
    return Text('The current count is: $counter', style: TextStyle(fontSize: 18));
  }
}

如何访问和修改外部状态

子组件访问父组件的状态

子组件访问父组件的状态很简单,只需要将父组件的状态作为参数传递给子组件即可。

dart
class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ChildWidget(counter: _counter),
      ],
    );
  }
}

class ChildWidget extends StatelessWidget {
  final int counter;

  ChildWidget({required this.counter});

  @override
  Widget build(BuildContext context) {
    return Text('Counter: $counter');
  }
}

子组件修改父组件的状态

子组件修改父组件状态也很简单,通过传递一个回调函数给子组件,子组件在需要修改状态时调用该回调函数即可。

dart
class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ChildWidget(onIncrement: _incrementCounter),
      ],
    );
  }
}

class ChildWidget extends StatelessWidget {
  final Function onIncrement;

  ChildWidget({required this.onIncrement});

  @override
  Widget build(BuildContext context) {
    return TextButton(onPressed: onIncrement, child: Text('Increment'));
  }
}

所谓「控制器」到底是什么

示例

当我们使用 TextField 时,我们通常会使用 TextEditingController 来管理文本框的状态。我们可以通过 TextEditingControllertext 属性来获取和设置文本框的文本内容。并且监听 TextEditingControllertext 属性变化。

dart
TextEditingController _controller = TextEditingController();

_controller.text = 'Hello, World!';

_controller.addListener(() {
  print(_controller.text);
});


TextField(
  controller: _controller,
)

自定义一个Controller

下面我们通过自己定义一个Controller来实现一个计数器。从而理解所谓的「控制器」到底是什么。

dart
class CounterController extends ChangeNotifier {
  int _counter = 0;

  int get counter => _counter;

  set counter(int value) {
    _counter = value;
    notifyListeners();
  }

  void increment() {
    _counter++;
    notifyListeners();
  }
}


class CounterWidget extends StatelessWidget {


  final CounterController controller;

  CounterWidget({required this.controller});

  @override
  Widget build(BuildContext context) {
    return ListenableBuilder(
      listenable: controller,
      builder: (context, child) {
        return Text('Counter: ${controller.counter}');
      },
    );
  }
}

main() {
  runApp(
    MyApp(),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {

  final CounterController controller = CounterController();


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter App')),
      body: Column(
        children: [
          CounterWidget(controller: controller),
          TextButton(onPressed: () {
            controller.increment();
          }, child: Text('Increment')),
        ],
      ),
    );
  }
}

详解 ChangeNotifier 机制

从上面可以看到,ChangeNotifier 机制的核心在于 notifyListeners 方法。每当状态发生变化时,我们都需要调用 notifyListeners 方法来通知所有监听该状态的组件。

至于组件如何监听状态的变化,我们使用了 ListenableBuilder 组件。当状态发生变化时,ListenableBuilder 组件会重新构建。从而实现状态的变化。

除了 ChangeNotifier 之外,还有 ValueNotifer 等组件,用于管理状态。

继承式组件 InheritedWidget

InheritedWidget 是 Flutter 中的一种组件,用于在组件树中共享状态。通过继承 InheritedWidget 组件,我们可以将状态传递给子组件。

示例

dart
import 'package:flutter/material.dart';

class MyColor extends InheritedWidget {
  final Color color;
  final Function() changeColor;

  MyColor({
    required this.color,
    required this.changeColor,
    required Widget child,
  }) : super(child: child);

  @override
  bool updateShouldNotify(MyColor oldWidget) {
    return oldWidget.color != color;
  }

  static MyColor of(BuildContext context) {
    // 根据类型获取最近的 MyColor 组件 并 添加依赖
    // 对应的还有 `context.getInheritedWidgetOfExactType<MyColor>()` 获取最近的 MyColor 组件 但不添加依赖 
    return context.dependOnInheritedWidgetOfExactType<MyColor>()!;
  }
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  Color _color = Colors.blue;

  void _changeColor() {
    setState(() {
      _color = _color == Colors.blue ? Colors.red : Colors.blue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyColor(
        color: _color,
        changeColor: _changeColor,
        child: Scaffold(
          appBar: AppBar(title: Text('MyColor Demo')),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const ColorDisplayWidget(),
                SizedBox(height: 20),
                ElevatedButton(
                  onPressed: _changeColor,
                  child: Text('Change Color'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class ColorDisplayWidget extends StatelessWidget {

  const ColorDisplayWidget({super.key});

  @override
  Widget build(BuildContext context) {
    final myColor = MyColor.of(context);
    return Container(
      width: 100,
      height: 100,
      color: myColor.color,
      child: Center(
        child: Text(
          'Current Color',
          style: TextStyle(color: Colors.white),
        ),
      ),
    );
  }
}

void main() {
  runApp(MyApp());
}

上面的例子中 ColorDisplayWidget 是一个 const 组件,但是当我们点击按钮之后,ColorDisplayWidget 的颜色仍然能够发生变化。就是因为我们通过 InheritedWidget 的方式获取了color的值

dependOnInheritedWidgetOfExactType 方法被调用时,Flutter 会自动将当前的 BuildContext 添加到 InheritedWidget 的依赖列表中。当 InheritedWidget 的状态发生变化时,Flutter 会自动调用 updateShouldNotify 方法,从而通知所有依赖于该状态的组件重新构建。 这时 子组件的 生命周期方法 didChangeDependencies 会被调用。

另外我们可以通过 updateShouldNotifyDependents 方法来决定是否通知所有依赖于该状态的组件重新构建。从而实现更精细化的状态管理。

dart

class MyColor extends InheritedWidget {
  final Color color;
  final Function() changeColor;

  MyColor({
    required this.color,
    required this.changeColor,
    required Widget child,
  }) : super(child: child);

  @override
  bool updateShouldNotify(MyColor oldWidget) {
    return oldWidget.color != color;
  }


  @override
  bool updateShouldNotifyDependents(MyColor oldWidget, Set<Object> dependencies) {
    // 如果依赖列表发生变化,则通知所有依赖于该状态的组件重新构建
    if (dependencies.contains('color')) {
      return oldWidget.color != color;
    }
    return false;
  }

  static MyColor of(BuildContext context) {
    // 根据类型获取最近的 MyColor 组件 并 添加依赖
    // 对应的还有 `context.getInheritedWidgetOfExactType<MyColor>()` 获取最近的 MyColor 组件 但不添加依赖 
    return context.dependOnInheritedWidgetOfExactType<MyColor>()!;
  }
}

自己动手做个 Provider

我们的项目目录结构如下:

plaintext
lib
├── change_notifier_provider.dart
├── main.dart
├── model
│   └── logo_model.dart
└── ui
    ├── control_panel.dart
    └── logo.dart
dart
// change_notifier_provider.dart
import 'package:flutter/material.dart';

class ChangeNotifierProvider<T extends Listenable> extends StatefulWidget {
  const ChangeNotifierProvider(
      {super.key, required this.create, required this.child});

  final Widget child;
  final T Function(BuildContext context) create;

  @override
  State<ChangeNotifierProvider> createState() =>
      _ChangeNotifierProviderState<T>();



  static T of<T>(BuildContext context, {required bool listen}) {
    if (listen) {
      return context.dependOnInheritedWidgetOfExactType<_InnerInheritedWidget<T>>()!.model;

    } else {
      return context.getInheritedWidgetOfExactType<_InnerInheritedWidget<T>>()!.model;

    }
  }

}

class _ChangeNotifierProviderState<T extends Listenable>
    extends State<ChangeNotifierProvider<T>> {
  late T model;

  @override
  void initState() {
    super.initState();
    model = widget.create(context);
  }

  @override
  Widget build(BuildContext context) {
    return ListenableBuilder(
      listenable: model,
      builder: (BuildContext context, Widget? child) {
        return _InnerInheritedWidget(
          model: model,
          child: child!,
        );
      },
      child: widget.child,
    );
  }
}

class _InnerInheritedWidget<T> extends InheritedWidget {
  const _InnerInheritedWidget({super.key, required super.child, required this.model});

  final T model;

  @override
  bool updateShouldNotify(covariant InheritedWidget oldWidget) {
    return true;
  }
}

extension ChangeNotifierProviderExtension on BuildContext {
  T watch<T extends Listenable>() {
    return ChangeNotifierProvider.of<T>(this, listen: true);
  }

  T read<T extends Listenable>() {
    return ChangeNotifierProvider.of<T>(this, listen: true);
  }
}
dart
// model/logo_model.dart
import 'package:flutter/material.dart';

class LogoModel extends ChangeNotifier {
  bool _flipX = false;

  bool _flipY = false;

  double _size = 50;

  bool get flipX => _flipX;

  set flipX(bool value) {
    _flipX = value;
    notifyListeners();
  }

  bool get flipY => _flipY;

  set flipY(bool value) {
    _flipY = value;
    notifyListeners();
  }

  double get size => _size;

  set size(double value) {
    _size = value;
    notifyListeners();
  }
}
dart
// ui/control_panel.dart
import 'package:flutter/material.dart';
import '/change_notifier_provider.dart';
import '/model/logo_model.dart';

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

  @override
  State<ControlPanel> createState() => _ControlPanelState();
}

class _ControlPanelState extends State<ControlPanel> {
  @override
  Widget build(BuildContext context) {
    var model = context.watch<LogoModel>();
    return Card(
      margin: const EdgeInsets.all(32),
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Text('Size'),
                Slider(
                  value: model.size,
                  min: 0,
                  max: 100,
                  onChanged: (value) {
                    model.size = value;
                  },
                ),
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Text('FlipX'),
                Switch(
                  value: model.flipX,
                  onChanged: (value) {
                    model.flipX = value;
                  },
                ),
                const SizedBox(width: 32),
                const Text('FlipY'),
                Switch(
                  value: model.flipY,
                  onChanged: (value) {
                    model.flipY = value;
                  },
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
dart
// ui/logo.dart
import 'package:flutter/material.dart';
import '/change_notifier_provider.dart';
import '/model/logo_model.dart';

class Logo extends StatelessWidget {
  const Logo({super.key});

  @override
  Widget build(BuildContext context) {
    var model = context.read<LogoModel>();
    return Card(
      child: Transform.flip(
        flipX: model.flipX,
        flipY: model.flipY,
        child: FlutterLogo(size: model.size + 50.0),
      ),
    );
  }
}

dart
// main.dart
import 'package:flutter/material.dart';
import 'change_notifier_provider.dart';
import 'model/logo_model.dart';
import 'ui/control_panel.dart';
import 'ui/logo.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primaryColor: Colors.blue,
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home Page')),
      body: Center(
        child: ChangeNotifierProvider<LogoModel>(
          create: (BuildContext context) => LogoModel(),
          child: const Column(
            children: [
              Logo(),
              ControlPanel(),
            ],
          ),
        ),
      ),
    );
  }
}

效果

image

参考资料

  1. 状态提升(Lifting-state-up)
  2. 如何访问和修改外部状态
  3. 所谓「控制器」到底是什么
  4. 详解ChangeNotifier机制
  5. 继承式组件InheritedWidget
  6. 自己动手做个 Provider
  7. 利用泛型做通用封装