likes
comments
collection
share

懂 Vue、React 就懂 Flutter 状态管理

作者站长头像
站长
· 阅读数 14

嘿!你是不是经常在开发Flutter应用的时候被状态搞得头大?别担心,我也是,每次面对不同的flutter项目应用着不同的状态管理方案在阅读时总会有一定的心智负担,尤其是总觉得 flutter 状态管理要比 Vue, React 更难? 其实不是的,本文梳理了一下常见的 flutter 状态管理实践,帮助大家加深一下flutter状态的理解。

状态提升

即使我们不引入任何状态管理的库,只要我们继承 StatefulWidget 创建了一个 State 对象,我们就已经开始了状态管理。

class MyStatefulWidget extends StatefulWidget { 
    @override _MyStatefulWidgetState createState() => _MyStatefulWidgetState(); 
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> { 
    String _displayText = 'Hello, world!'; 
    void _updateText() { 
        setState(() { 
            _displayText = 'something changed!'; 
        }); 
    }

StatefulWidget 由两部分组成:一个是 StatefulWidget 类,另一个是对应的 State 类。StatefulWidget 类负责定义小部件的结构和配置,而 State 类负责跟踪和管理小部件的状态。

StatefulWidget 可以包含一个或多个 State 对象,每个 State 对象都与 StatefulWidget 对象相关联,并负责处理小部件的状态变化和重建。在本例中,我们的状态就是 Line 6_displayText, 我们定义一个方法 _updateText, 我们可以给这个方法绑定一个交互事件,比如点击按钮,点击键盘,滑动屏幕等,只要交互触发了这个 _updateText 方法,我们就通过 setState 改变了 _displayText 的值。是不是和 Class 时代的 React 一模一样?

在Flutter中,有了stateful widgets,我们可以在小部件内部管理它的状态。但是,当我们的应用程序变得更加复杂时,问题就会发生了。状态可能会在不同的小部件之间共享,或者我们可能需要在不同的小部件之间进行状态同步。在 React 中我们怎么办呢?状态提升!

将状态提升到共同的父级小部件:如果两个小部件之间需要共享状态,可以将该状态提升到它们的共同父级小部件中。然后,父级小部件可以传递该状态给它们,并在需要时更新状态。这样,两个小部件就可以共享同一个状态。

举个例子,2个组件需要同步状态,Text 组件需要显示当前 switch 的状态,比如开启还是关闭, 另一个 switch 组件负责更改状态,那这2个组件在同一个UI中应该如何同步呢? 我们需要将 switch 的状态提取到父级来控制,将switch 这个子组件变成无状态组件,父级统一来同步 switch 状态的变更。

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

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

  void _toggleSwitch(bool newValue) {
    setState(() {
      _isSwitchOn = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ChildWidget(
          isSwitchOn: _isSwitchOn,
          toggleSwitch: _toggleSwitch,
        ),
        Text('Switch is ${_isSwitchOn ? 'ON' : 'OFF'}'),
      ],
    );
  }
}

class ChildWidget extends StatelessWidget {
  final bool isSwitchOn;
  final Function(bool) toggleSwitch;

  ChildWidget({required this.isSwitchOn, required this.toggleSwitch});

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

在这个示例中,ParentWidget 是一个有状态的小部件,它包含一个布尔值 _isSwitchedOn 作为状态。ChildWidget 是一个无状态的小部件,它显示一个开关,其值由父组件的状态控制。

当用户在 ChildWidget 上切换开关时,会调用父组件的 _toggleSwitch 方法,从而更新 _isSwitchedOn 的值,值变化会导致重新构建父组件的小部件树。这样,ChildWidgetText 小部件都会反映出新的开关状态。

但是,当组件状态共享情况变得复杂时,提升状态会变得困难,尤其是组件嵌套层级很深的2个组件要共享状态,那要提升到最父级然后层层透传状态维护起来简直是灾难。

这时候该全局状态管理库出场了,状态管理工具可以帮助我们更好地组织和管理应用程序中的状态。它们提供了一种方式来共享和跟踪状态,使我们的代码更易于维护和扩展。一些常见的状态管理解决方案包括Provider、Redux和GetX。

Provider

我们来详细介绍一下 Provider,它基于InheritedWidget和ChangeNotifier的组合。可以创建一个继承自ChangeNotifier的类来表示应用程序状态。

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

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

我们的状态就是 _count, 以及状态变更方法 increment, 当有人调用 increment后我们的 _count 会+1,同时我们也通知所有的订阅者,告诉他们这个 _count 值发生了改变,需要重新渲染 UI。

在我们的Flutter应用程序中,我们可以使用ChangeNotifierProvider来提供状态给需要访问它的小部件,在下面这个例子中,我们将 Counter 的状态提供给 MyApp 组件。

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => Counter(),
      child: MyApp(),
    ),
  );
}

Provider还提供了一些便利的方法,如Provider.of和Consumer,可以使用Provider.of方法来获取状态,并在小部件树中的任何位置访问它。而Consumer小部件则允许你对状态进行监听和响应。

例如下面,我们使用Consumer小部件来订阅 Counter 状态的变化,只要 _count 值变化,这个Text 显示的文本就会变化。我们通过 Provider.of 来获取 Counter 的状态,和触发 Counter 状态的变更。


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Provider Example'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'Count:',
              ),
              Consumer<Counter>(
                builder: (context, counter, child) => Text(
                  '${counter.count}',
                  style: TextStyle(fontSize: 24),
                ),
              ),
              RaisedButton(
                child: Text('Increment'),
                onPressed: () {
                  Provider.of<Counter>(context, listen: false).increment();
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

当我们的程序状态比较多,我们可以建立多个 provider 来管理应用的数据

MultiProvider(
  providers: [
    ChangeNotifierProvider<Counter>(create: (_) => Counter()),
    ChangeNotifierProvider<ThemeModel>(create: (_) => ThemeModel()),
    // 添加更多的Provider
  ],
  child: MyApp(),
)

在 React 中我们为了避免重复的 UI 渲染,我们通常会只订阅有变化的数据,在 Flutter 中也一样,我们可以通过 Selector 选择性地监听特定的状态变化, 来避免没有意义的重新渲染 UI或者实现一些自定渲染逻辑。当然这里需要提醒的是,如果组件数不复杂,防止重不是性能的瓶颈。

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

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer2<MyProvider, AnotherProvider>(
      builder: (context, myProvider, anotherProvider, child) {
        // 根据两个状态进行自定义逻辑
        String message = myProvider.count > 5 && anotherProvider.isReady
            ? '条件满足'
            : '条件不满足';
        return Text(
          message,
          style: TextStyle(fontSize: 20),
        );
      },
      selector: (context, myProvider, anotherProvider) =>
          '${myProvider.count}${anotherProvider.isReady}',
    );
  }
}

class MyProvider with ChangeNotifier {
  int _count = 0;

  int get count => _count;

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

class AnotherProvider with ChangeNotifier {
  bool _isReady = false;

  bool get isReady => _isReady;

  void setReady(bool value) {
    _isReady = value;
    notifyListeners();
  }
}

在这个例子中,MyWidget订阅了MyProvider和AnotherProvider两个状态。当count和isReady满足一定条件时,Text小部件会显示"条件满足",否则显示"条件不满足"。通过使用Selector,我们可以自定义选择逻辑,并且只有在选择的状态发生变化时才会重新构建Text小部件。这样可以避免不必要的重建,提高性能。

最后来分享一下 PorxyProvider 来处理更复杂的情况,比如:

  • 当一个状态依赖于其他状态的计算结果时,可以使用ProxyProvider来根据依赖的状态计算出新的状态。
  • 当某个状态需要经过复杂的逻辑处理,而不仅仅是简单的属性获取时,可以使用ProxyProvider来处理逻辑并提供新的状态给子部件使用。
  • 当某个状态需要在多个子部件中共享时,可以使用ProxyProvider来提供该状态给多个子部件使用。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ProxyProvider<MyProvider, AnotherProvider>(
      update: (context, myProvider, anotherProvider) {
        // 根据MyProvider计算得到AnotherProvider的值
        bool isReady = myProvider.count > 10;
        return AnotherProvider(isReady);
      },
      child: Consumer<AnotherProvider>(
        builder: (context, anotherProvider, child) {
          return Text(
            'Is Ready: ${anotherProvider.isReady}',
            style: TextStyle(fontSize: 20),
          );
        },
      ),
    );
  }
}

class MyProvider with ChangeNotifier {
  int _count = 0;

  int get count => _count;

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

class AnotherProvider with ChangeNotifier {
  bool _isReady;

  bool get isReady => _isReady;

  AnotherProvider(this._isReady);
}

还是和之前的例子类似,我们改为ProxyProvider,MyWidget使用了ProxyProvider和Consumer。ProxyProvider可以根据一个或多个依赖的状态计算得到新的状态,并提供给后续的子部件。在这个例子中,ProxyProvider会根据MyProvider中的count属性计算出AnotherProvider的isReady属性。当count大于10时,isReady为true,否则为false。

结尾

状态管理在构建强大和可扩展的 Flutter 应用时非常重要,会让我们的代码变得更容易维护。Stateful Widgets、Provider 或者 BLoC 模式只是 Flutter 中的几种状态管理方法。每种方法没有好坏,只有合适的场景和个人的偏好。

如果你觉得这篇文章有帮助,请点个赞和关注吧❤️!我将持续分享更多有价值的技术知识和经验。谢谢!

转载自:https://juejin.cn/post/7283813865719087143
评论
请登录