• Flutter 的状态管理


    状态提升(Lifting-state-up)

    子组件的状态,提升到上级组件中,从而实现在多个组件之间共享和同步数据的效果

    flutter counter demo,那个按按钮+1 的来说,现在的 count 是几,不是存在页面显示几的地方,而是作为 HomePage 的一个 state,这样就提到了上级;子组件那个按钮的 press 事件,也不是说找到页面显示几的 Text 元素,然后改那个元素,而是改 state

    子组件获取和控制父组件的状态

    父组件传给子组件,只需要直接传参数进去即可

    class Child extends StatelessWidget {
        final int stateFromParent;
    
        const Child({Key? key, required this.stateFromParent}): super(key: key);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    子组件想要修改父组件的变量,需要使用 callback。在 dart 中,函数也可以作为参数传递,

    class Child extends StatelessWidget {
        final void Function() changeStateInParent;
    
        const Child({Key? key, required this.changeStateInParent}): super(key: key);
    
        
        Widget build(BuildContext context) {
            return ElevatedButton(
                onPressed: changeStateInParent
            );
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    关于 Funtion 类型

    final void Function() changeStateInParent;
    
    • 1

    void 是代表这个函数的返回值
    ()是代表这个函数没有参数

    为什么是这个顺序呢,因为我们平时写函数的时候也是这样

    int fun(int x, int y){
        ...
    }
    
    • 1
    • 2
    • 3

    那么这个 fun 函数的函数类型就是 int Function(int x, int y)

    void Function()可以缩写为 VoidCallback,因为 flutter 里面有如下定义

    typedef VoidCallback = void Function();
    
    • 1

    关于 BuildContext

    BuildContext 在 flutter 中是用于定位当前 Widget 在 Widget 树中位置的对象,用于访问父 widget 和其他相关信息,在构建 UI 时调用

    比如,用 Nevigator 进行页面导航的时候,需要使用 BuildContext 来获取当前 Scaffold(页面基本元素布局,如 appBar 之类的)或 MaterialApp,以执行页面跳转操作

    BuildContext 还可用于查找和访问在 widget 树中的其他 widget

    onPressed: () {
        Scaffold scaffold = Scaffold.of(context);
        scaffold.showSnackBar(SnackBar(content: Text('Button Pressed')))
    }
    
    • 1
    • 2
    • 3
    • 4

    控制器(父组件控制子组件的状态)

    第一个思路,状态提升,将子组件的状态提升到父组件上,可以,但是有一些问题

    1. 子组件不是我们自己写的,而是用的别人的库,这样就没办法要求它提升到我们的父组件了
    2. 而且我们封装子组件的目的就是为了提高性能,结果提升到父组件了,又要整体进行重绘,如拆
    3. 如果这个状态是基础数据类型,那么父组件给子组件传递的是值,是一个副本,子组件去修改这个值的时候,修改不到父组件的版本

    解决状态提升的基础数据类型问题

    基础数据类型下,父组件给子组件传递的是值不是引用,父组件控制子组件的功能是好的,但是如果子组件想正常的管理自己的 state,就通知不到父组件

    解决方法是将 state 从基础数据类型转换为一个复杂的结构,这样传的就是引用,而不是值了

    class IntHolder {
        int value;
        IntHolder(this.value);
    }
    
    class _ParentState extends State<Parent> {
        IntHolder ih = IntHolder(1);
        Widget build(BuildContext context) {
            return Child(ih);
        }
    }
    
    class Child extends StatefulWidget {
        final IntHolder ih;
        const Child({Key? key, required this.ih});
    }
    
    class _ChildState extends State<Child> {
        
        Widget build(BuildContext context){
            return Column(children: [
                Text(widget.ih.value);
                ElevatedButton(
                    onPressed: () {
                        setState(() {widget.ih.value = 2; });
                    }
                );
            ]);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    解决整体重绘的问题

    子组件可以监听一个 stream,当 stream 发生变化的时候,这个子组件也可以发生变化,这个解决方案有点太“重”了,我们这个假如就是传递一个数,结果整了一个 stream,没必要

    如果父组件有这么一个功能,当某个 state 变化的时候,能够“通知”相对应的子组件让它变化,这个过程中父组件自身不变,就可以完美解决这个问题了

    这就要求 state 不是直接以 state 形式被提升在父组件里,而是被封装起来。即使里面的值发生变化,从父组件的角度看,这个被封装起来的块没有发生变化,就不会引发父组件重绘;同时,里面的值发生变化后,又要能够通知子组件,让子组件进行重绘

    flutter 有一个 ChangeNotifier 类,可以有这样的效果,当 object 更新时,通知子组件更新

    下面的例子是官方文档举的例子,其中 CounterModel 就混入了 ChangeNotifier,当_count 变化时,会通知这个 notifier 所在的 ListenableBuilder,然后 ListenableBuilder 重新使用 builder 进行 build

    // 这里的 with 相当于 mixin,直接混入进去,如 class Dove extends Bird with Walker, Flyer {}
    class CounterModel with ChangeNotifier {
      int _count = 0;
      int get count => _count;
    
      void increment() {
        _count += 1;
        notifyListeners(); // 在 _count 变化后通知
      }
    
      // 下面还能再加一些其他的更新 _count 的逻辑
    }
    
    class ListenableBuilderExample extends StatefulWidget {
      const ListenableBuilderExample({super.key});
    
      
      State<ListenableBuilderExample> createState() =>
          _ListenableBuilderExampleState();
    }
    
    class _ListenableBuilderExampleState extends State<ListenableBuilderExample> {
      final CounterModel _counter = CounterModel();
    
      
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(title: const Text('ListenableBuilder Example')),
            body: CounterBody(counterNotifier: _counter),
            floatingActionButton: FloatingActionButton(
              onPressed: _counter.increment,
              child: const Icon(Icons.add),
            ),
          ),
        );
      }b
    }
    
    class CounterBody extends StatelessWidget {
      const CounterBody({super.key, required this.counterNotifier});
    
      final CounterModel counterNotifier;
    
      
      Widget build(BuildContext context) {
        return Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              const Text('Current counter value:'),
              // Thanks to the ListenableBuilder, only the widget displaying the
              // current count is rebuilt when counterValueNotifier notifies its
              // listeners. The Text widget above and CounterBody itself aren't
              // rebuilt.
              ListenableBuilder(
                listenable: counterNotifier, // 每当 counterNotifier 里 notifyListeners 后
                builder: (BuildContext context, Widget? child) { // 重新 build
                  return Text('${counterNotifier.count}');
                },
              ),
            ],
          ),
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    这个 CounterModel 就是一个控制器,可以直接把它重新命名为 CounterController


    如果是例子中这样,只为了一个变量实现一个类的 ChangeNotifier,有点繁琐。Flutter 为这种单变量值发生变化的 Notifier 提供了一个专门的类,ValueNotifier。CounterController 可以直接写成:

    class CounterController {
        ValueNotifier count = ValueNotifier(0); // ValueNotifier 本身继承了 ChangeNotifier
    }
    
    • 1
    • 2
    • 3

    注意,这样修改之后,count 变成了一个 Notifier,所以使用 count 值的时候,要写成 count.value

            ListenableBuilder(
                listenable: widget.controller.count, // 每当 ValueNotifier count 的value 变化
                builder: (BuildContext context, Widget? child) { // 重新 build
                    return Text('${widget.controller.count.value}');
                },
            ),
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果同时需要监听多个 Notifier 的变化,使用 Listenable.merge

            ListenableBuilder(
                listenable: Listenable.merge([
                    widget.controller.count,
                    widget.controller.fontSize
                ])
                builder: (BuildContext context, Widget? child) { // 重新 build
                    return Text(
                        '${widget.controller.count.value}',
                        style: TextStyle(fontSize: widget.controller.fontSize.value),
                    );
                },
            ),
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    如果是这样多个 Notifier,可以统一放在一起,提升到父组件中,这就是控制器类(Controller)

    组件在开发的时候,一般都遵循这个规范,使用 Controller。所以我们用别人写的组件的时候,只需要用他们写好的 Controller 就能实现父组件控制子组件的状态,且不影响子组件自己控制自己状态了

    继承式组件 InheritedWidget

    如果 state 被提升到顶部,就要一层一层传,这样,每一层组件的构造函数都有大量的参数

    InheritedWidget 可以解决这一问题

    class MyColor extends InteritedWidget {
        final Color color;
    
        MyColor({super.key, required super.child, required this.color});
    }
    
    // 在组件树较高的位置用 MyColor 包裹
    // 这样里面的所有组件都能访问到 color 这个属性
    class MyApp extends StatelessWidget {
        const MyApp({Key? key}): super(key: key);
    
        
        Widget build(BuildContext context) {
            return MyColor(
                child: MaterialApp(
                    ....
                ),
                color: Color.red
            );
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    dependOnInheritedWidgetOfExactType:依赖于 继承式组件 of 特定的 Type
    因为在某个组件所在的那一支上可能有其他的继承式组件,我们要找那个特定的继承式组件(这里 MyColor)

    具体的寻找方法,就是从这个组件向上,向组件树的根部去找,直到找到 ExactType
    如果有多个同样的组件(MyColor),选最近的那一个

    组件如何访问到这个继承式组件的属性呢

    Widget build(BuildContext context) {
        final myColor = context.dependOnInheritedWidgetOfExactType<MyColor>();
    
        return Container(
            color: myColor.color,
            ...
        );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    关于 updateShouldNotify

    InteritedWidget 有一个函数 updateShouldNotify,是指当 InheritedWidget 发生变化的时候,需不需要通知相关的子组件进行重绘

    按理说,变了肯定要通知,要不然不白变了吗

    但是有的时候,InteritedWidget 的属性是被 setState 变掉的,setState 本身就会让子组件刷新,所以不用通知,子组件本来就是新的

    class MyColor extends InteritedWidget {
        final Color color;
    
    	// color 是父组件传进来的,是父组件的 state。父组件 setState 后子组件也重绘了
        MyColor({super.key, required super.child, required this.color});
    
        
        bool updateShouldNotify(covariant Inherited oldWidget) {
            // return true;
            return false; // 颜色也会变化,不过不是 updateShouldNotify 导致的
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    虽然能够成功更新颜色,但这种一 setState,全局组件就要重绘,不是我们想看到的,所以要多加 const

    return const Child();
    
    • 1

    这样的话父组件里的 state 发生变化,子组件就不会自动重绘了
    这样 updateShouldNotify 的重要性就凸显出来了

    
    bool updateShouldNotify(covariant Inherited oldWidget) {
        return color != oldWidget.color;
    }
    
    • 1
    • 2
    • 3
    • 4

    当新的 color 不等于旧的 color 时,告诉子组件刷新


    如果是希望获取一次 color 后就不管了,不再监听之后 color 的更新,可以使用 getInheritedWidgetOfExactType

        final myColor = context.getInheritedWidgetOfExactType<MyColor>();
    
    • 1

    在 color 更新后,如果 updateShouldNotify 为 true,且子组件是 dependOnInheritedWidgetOfExactType,也会调用 didChangeDependencies 这个生命周期函数

    关于 of

    如果能将 dependOnInheritedWidgetOfExactType 封装起来,不用每次子组件使用的时候都写这么长一串,那就更好了。直接将这个封装到 MyColor 这个继承式组件中

    有两种,of 和 maybeOf
    of 指一定能从这个 context (组件树往上找)中找到 MyColor
    maybeOf 指有可能能找到,可能为空

    class MyColor extends InteritedWidget {
        final Color color;
    
        MyColor({super.key, required super.child, required this.color});
    
        static MyColor of(BuildContext context) {
            return context.dependOnInheritedWidgetOfExactType<MyColor>()!;
            // !代表确信不为空
        }
    
        // 或
        static MyColor maybeOf(BuildContext context) {
            return context.dependOnInheritedWidgetOfExactType<MyColor>();
        }
    
        
        bool updateShouldNotify(covariant Inherited oldWidget) {
            return color != oldWidget.color;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    通过这种写法,子组件找到继承式组件就能更加简洁了

    color: MyColor.of(context).color;
    
    • 1
  • 相关阅读:
    深入理解Angular模块化概念
    文本关键信息抽取-面向复杂文本结构的实体关系联合抽取研究(论文研读)(二)
    DVWA 反射型XSS XSS(Reflected)题解
    JVM(Java虚拟机)笔记
    关于java语言中的final关键字
    PIR人体感应AC系列感应器投光灯人体感应开关等应用定制方案
    CentOS7 安装 ElasticSearch7.10
    qml+QQuickPaintedItem笛卡尔坐标和屏幕坐标的转换
    C语言 实现 链 显示 效果 查找 修改 删除
    【元宇宙欧米说】音频+PFP,做一只web3最勤劳的猫咪
  • 原文地址:https://blog.csdn.net/weixin_43544179/article/details/136390580