【Flutter#状态管理】Flutter 状态管理全面解析

189 阅读5分钟

文章目录

  • 一、什么是状态?
    • 1、状态的种类
    • 2、状态管理交给谁
      • 2.1状态交给自己管理
      • 2.2状态交给父Widget管理
      • 2.3混合状态管理
      • 2.4全局状态管理
  • 二、Flutter 中常见的状态管理方式
    • 1、setState
      • 1.1 setState特点
      • 1.2 setState构建
      • 1.3 setState构建优化
    • 2、InheritedWidget / InheritedModel
      • 2.1 InheritedWidget
      • 2.2 InheritedModel
    • 3、Provider
      • 3.1添加依赖
      • 3.2创建登录管理状态模型
      • 3.3创建登录页面
      • 3.4运行应用
    • 4、Riverpod
      • 4.1什么是 Riverpod
      • 4.2使用 Riverpod 实现登录页面
          1. 添加依赖
          1. 创建登录逻辑
          1. 构建登录页面UI
          1. 主入口
    • 5、Bloc (Business Logic Component)
      • 5.1 为什么使用 Bloc
      • 5.2 Bloc 核心概念
      • 5.3 在 Flutter 中使用 Bloc
          1. 添加依赖
          1. 创建 CounterCubit
          1. 使用 CounterCubit 构建 UI
    • 6、Redux
      • 6.1 Redux 核心概念
          1. Store
          1. State
          1. Action
          1. Reducer
          1. Middleware
      • 6.2 Redux 的工作流程
      • 6.3 在 Flutter 中使用 Redux
    • 7、GetX
      • 7.1 GetX状态管理
      • 7.2 GetX路由
      • 7.3 GetX依赖管理
      • 7.4 GetX国际化

响应式的编程框架中都会有一个永恒的主题——"状态(State)管理",无论是在 React/Vue 还是 Flutter 中,他们讨论的问题和解决的思想都是一致的。状态管理是 Flutter 开发中的一个核心概念,因为 Flutter 的 UI 构建是基于状态的。

一、什么是状态?

在 Flutter 中,状态指的是影响 UI 展示的数据。例如,用户的输入、应用的配置、从服务器获取的数据等。Flutter 的 UI 是响应式的,意味着 UI 会随着状态的变化而自动更新。

1、状态的种类

局部状态:与某个特定 widget 相关的状态,只影响当前 widget 的渲染。
全局状态:跨多个 widget 的状态,通常需要跨多个页面或组件共享。

2、状态管理交给谁

  1. Widget 管理自己的状态。
  2. Widget 管理子 Widget 状态。
  3. 混合管理(父 Widget 和子 Widget 都管理状态)。

如何决定使用哪种管理方法?下面是官方给出的一些原则可以帮助你做决定:

  1. 如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父 Widget 管理。
  2. 如果状态是有关界面外观效果的,例如颜色、动画,那么状态最好由 Widget 本身来管理。
  3. 如果某一个状态是不同 Widget 共享的则最好由它们共同的父 Widget 管理。

2.1状态交给自己管理

// TapboxA 管理自身状态
class TapboxA extends StatefulWidget {
  TapboxA({Key? key}) : super(key: key);

  @override
  _TapboxAState createState() => _TapboxAState();
}

class _TapboxAState extends State<TapboxA> {
  bool _active = false;

  void _handleTap() {
    setState(() {
      _active = !_active;
    });
  }

  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        child: Center(
          child: Text(
            _active ? 'Active' : 'Inactive',
            style: TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: _active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
      ),
    );
  }
}

2.2状态交给父Widget管理

// ParentWidget 为 TapboxB 管理状态
class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  bool _active = false;

  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: TapboxB(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}

class TapboxB extends StatelessWidget {
  TapboxB({Key? key, this.active: false, required this.onChanged})
      : super(key: key);

  final bool active;
  final ValueChanged<bool> onChanged;

  void _handleTap() {
    onChanged(!active);
  }

  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        child: Center(
          child: Text(
            active ? 'Active' : 'Inactive',
            style: TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
      ),
    );
  }
}

2.3混合状态管理

class ParentWidgetC extends StatefulWidget {
  @override
  _ParentWidgetCState createState() => _ParentWidgetCState();
}

class _ParentWidgetCState extends State<ParentWidgetC> {
  bool _active = false;

  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: TapboxC(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}

class TapboxC extends StatefulWidget {
  TapboxC({Key? key, this.active: false, required this.onChanged})
      : super(key: key);

  final bool active;
  final ValueChanged<bool> onChanged;
  
  @override
  _TapboxCState createState() => _TapboxCState();
}

class _TapboxCState extends State<TapboxC> {
  bool _highlight = false;

  void _handleTapDown(TapDownDetails details) {
    setState(() {
      _highlight = true;
    });
  }

  void _handleTapUp(TapUpDetails details) {
    setState(() {
      _highlight = false;
    });
  }

  void _handleTapCancel() {
    setState(() {
      _highlight = false;
    });
  }

  void _handleTap() {
    widget.onChanged(!widget.active);
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTapDown: _handleTapDown,
      onTapUp: _handleTapUp,
      onTap: _handleTap,
      onTapCancel: _handleTapCancel,
      child: Container(
        child: Center(
          child: Text(
            widget.active ? 'Active' : 'Inactive',
            style: TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: widget.active ? Colors.lightGreen[700] : Colors.grey[600],
          border: _highlight
              ? Border.all(
                  color: Colors.teal[700],
                  width: 10.0,
                )
              : null,
        ),
      ),
    );
  }
}

2.4全局状态管理

当应用中需要一些跨组件(包括跨路由)的状态需要同步时,上面介绍的方法便很难胜任了。正确的做法是通过一个全局状态管理器来处理这种相距较远的组件之间的通信。目前主要有两种办法:

  1. 实现一个全局的事件总线,将语言状态改变对应为一个事件,然后在APP中依赖应用语言的组件的initState方法中订阅语言改变的事件。
  2. 使用一些专门用于状态管理的包,如 Provider、Redux,或者使用专业的开发工具如 AppUploader 来管理应用状态。

二、Flutter 中常见的状态管理方式

1、setState
2、InheritedWidget / InheritedModel
3、Provider
4、Riverpod
5、Bloc (Business Logic Component)
6、Redux
7、GetX

接下来我们依次讲解这些状态管理方式。

1、setState

class _HomePageState extends State<HomePage> {
  int counter = 0;

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('setState Example')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Counter: $counter'),
            ElevatedButton(
              onPressed: _incrementCounter,
              child: Text('Increment'),
            ),
          ],
        ),
      ),
    );
  }
}

1.1 setState特点

setState 的特点:

  1. 适用于简单的状态管理。
  2. 每次调用 setState 会导致 widget 的重建。
  3. 它是最基础的状态管理方式,但不适用于复杂的应用

1.2 setState构建

当setState被调用时,它会将_dirty标志位设置为true。这个标志位的改变会通知 Flutter 框架,当前State对象所对应的Widget需要重新构建。

1.3 setState构建优化

  1. Flutter 有一套优化机制来避免不必要的重建。它会根据Widget的类型和属性来判断是否真的需要重新构建。
  2. 对于StatefulWidget,Flutter 会比较State对象的runtimeType和key属性。
  3. 频繁调用setState可能会导致性能问题

2、InheritedWidget / InheritedModel

2.1 InheritedWidget

import 'package:flutter/material.dart';

class MySharedDataWidget extends InheritedWidget {
  int sharedData;

  MySharedDataWidget({
    Key? key,
    required this.sharedData,
    required Widget child,
  }) : super(key: key, child: child);

  static MySharedDataWidget? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MySharedDataWidget>();
  }

  @override
  bool updateShouldNotify(MySharedDataWidget oldWidget) {
    return oldWidget.sharedData != sharedData;
  }
}

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

  @override
  Widget build(BuildContext context) {
    final int? sharedData = MySharedDataWidget.of(context)?.sharedData;
    return Text('共享的数据是: $sharedData', style: TextStyle(fontSize: 18));
  }
}

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

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

class _MyAppState extends State<MyApp> {
  int _sharedData = 42;

  void _updateSharedData() {
    setState(() {
      _sharedData++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('InheritedWidget示例'),
        ),
        body: Column(
          children: [
            MySharedDataWidget(
              sharedData: _sharedData,
              child: DataUsingWidget(),
            ),
            ElevatedButton(
              onPressed: _updateSharedData,
              child: const Text('更新共享数据'),
            ),
          ],
        ),
      ),
    );
  }
}

2.2 InheritedModel

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

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

class MyInheritedModelExample extends StatefulWidget {
  @override
  _MyInheritedModelExampleState createState() => _MyInheritedModelExampleState();
}

class _MyInheritedModelExampleState extends State<MyInheritedModelExample> {
  String text1 = 'Text 1';
  String text2 = 'Text 2';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('InheritedModel Example')),
      body: MyModel(
        text1: text1,
        text2: text2,
        child: Column(
          children: [
            ElevatedButton(
              onPressed: () {
                setState(() {
                  text1 = 'Updated Text 1';
                });
              },
              child: Text('Update Text 1'),
            ),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  text2 = 'Updated Text 2';
                });
              },
              child: Text('Update Text 2'),
            ),
            TextWidget1(),
            TextWidget2(),
          ],
        ),
      ),
    );
  }
}

class MyModel extends InheritedModel<String> {
  final String text1;
  final String text2;

  MyModel({required this.text1, required this.text2, required Widget child}) : super(child: child);

  @override
  bool updateShouldNotify(covariant MyModel oldWidget) {
    return text1 != oldWidget.text1 || text2 != oldWidget.text2;
  }

  @override
  bool updateShouldNotifyDependent(covariant MyModel oldWidget, Set<String> dependencies) {
    if (dependencies.contains('text1') && text1 != oldWidget.text1) {
      return true;
    }
    if (dependencies.contains('text2') && text2 != oldWidget.text2) {
      return true;
    }
    return false;
  }

  static MyModel? of(BuildContext context, String aspect) {
    return InheritedModel.inheritFrom<MyModel>(context, aspect: aspect);
  }
}

class TextWidget1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final model = MyModel.of(context, 'text1');
    return Text('Text 1: ${model?.text1}');
  }
}

class TextWidget2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final model = MyModel.of(context, 'text2');
    return Text('Text 2: ${model?.text2}');
  }
}

3、Provider

3.1添加依赖

dependencies:
  flutter:
    sdk: flutter
  provider: 6.0.0

3.2创建登录管理状态模型

import 'package:flutter/material.dart';

class LoginModel with ChangeNotifier {
  String _username = '';
  String _password = '';
  bool _isLoading = false;
  String? _errorMessage;

  String get username => _username;
  String get password => _password;
  bool get isLoading => _isLoading;
  String? get errorMessage => _errorMessage;

  void setUsername(String username) {
    _username = username;
    notifyListeners();
  }

  void setPassword(String password) {
    _password = password;
    notifyListeners();
  }

  Future<void> login() async {
    if (_username.isEmpty || _password.isEmpty) {
      _errorMessage = '用户名和密码不能为空';
      notifyListeners();
      return;
    }

    _isLoading = true;
    notifyListeners();

    await Future.delayed(Duration(seconds: 2));

    if (_username == 'admin' && _password == '123456') {
      _errorMessage = null;
    } else {
      _errorMessage = '用户名或密码错误';
    }

    _isLoading = false;
    notifyListeners();
  }
}

3.3创建登录页面

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

class LoginPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => LoginModel(),
      child: Scaffold(
        appBar: AppBar(title: Text('登录')),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Consumer<LoginModel>(
            builder: (context, loginModel, child) {
              return Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  TextField(
                    onChanged: loginModel.setUsername,
                    decoration: InputDecoration(
                      labelText: '用户名',
                      errorText: loginModel.errorMessage != null ? '错误: ${loginModel.errorMessage}' : null,
                    ),
                  ),
                  SizedBox(height: 16),
                  TextField(
                    onChanged: loginModel.setPassword,
                    obscureText: true,
                    decoration: InputDecoration(
                      labelText: '密码',
                      errorText: loginModel.errorMessage != null ? '错误: ${loginModel.errorMessage}' : null,
                    ),
                  ),
                  SizedBox(height: 16),
                  if (loginModel.isLoading)
                    Center(child: CircularProgressIndicator()),
                  if (!loginModel.isLoading)
                    ElevatedButton(
                      onPressed: () {
                        loginModel.login();
                      },
                      child: Text('登录'),
                    ),
                  if (loginModel.errorMessage != null)
                    Padding(
                      padding: const EdgeInsets.only(top: 16.0),
                      child: Text(
                        loginModel.errorMessage!,
                        style: TextStyle(color: Colors.red),
                      ),
                    ),
                ],
              );
            },
          ),
        ),