flutter 状态管理--InheritedWidget、Provider原理解析

  • 整体流程

    • 介绍状态管理的由来、发展历程、用法、源码
    • 最后在不使用三方库的情况下,通过控制器和继承式组件,实现一个Provide

为什么需要状态管理

  • 官方给的例子里,基础的事件响应是靠点击事件后更新参数,通过setState重新刷新整个页面,
  • 他会重新创建整个widget,调用build方法
  • 但是有些地方没必要更新,这个刷新就会造成一定程度的浪费

状态管理需要解决几个问题

  • 问题1:子组件如何访问外部的变量?

    • 通过传值
    • 如果嵌套层级过多,就要一层层往下传
  • 问题2:作为一个子组件如何修改外部的变量?

    • 通过回传函数callback
  • 问题3:A组件如何控制B组件的状态?

    • 通过控制器
    • 借助控制器,实现Button对TextField内文字的操作
less 复制代码
// 系统的控制器,用来控制文本
final _controller = TextEditingController();

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          TextField(
            controller: _controller,
          ),
          ElevatedButton(
            onPressed: () {
              _controller.clear();
            },
            child: const Text("clear"),
          ),
        ],
      ),
    ),
  );
}

控制器

  • 为什么用控制器

    • 不使用控制器的话,

      • 如果组件A需要控制组件B中属性,需要将B组件中的属性做状态提升,也就是将B组件中的属性提升到共有父组件
    • 这样做有三个问题

      • 第一,这要求我们对B组件要有完整的掌控能力,也就是说这枚组件的每一行代码都是我们自己写的,换句话说,如果这个组件不是我们自己写的,比如TextField,我们就不能让他把text信息暴露给我们,也就是说如果我们想为别人封装一枚可以独立运行的组件,就不能简单把内部状态提升出去
      • 第二,就算我们将状态提升出去,刷新的时候还是需要通过父组件的setState,这个会导致外面一起重绘
      • 第三,提升出去的属性有时候是基础类型,比如double,我们传递 到子组件的时候直接传递的话其实是值传递,要传递指针的话还需要封装成一个对象
  • 控制器是什么?手写一个控制器

    • 将属性封装成一个ChangeNotifier对象,在set数据的时候调用notifyListeners()
    • 将与属性相关的组件用ListenableBuilder包装起来,listenable参数绑定ChangeNotifier对象
    • set数据时,notifyListeners会通知到builder去刷新
less 复制代码
class _MyHomePageState extends State<MyHomePage> {
  // 自己写的控制器,研究控制器的原理
  DoubleHolder dh = DoubleHolder(0.3);
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              '对比系统的控制器,自己写的控制器,研究控制器的原理',
            ),
            Foo(dh: dh,),
            ElevatedButton(
              onPressed: () {
                dh.value = 1;
              },
              child: const Text("设为100"),
            ),
          ],
        ),
      ), 
    );
  }
}
scala 复制代码
class DoubleHolder extends ChangeNotifier{
  double _value;

  DoubleHolder(this._value);

  double get value => _value;

  set value(double newValue){
    _value = newValue;
    notifyListeners();
  }
}
scala 复制代码
class Foo extends StatefulWidget {
  final DoubleHolder dh;
  const Foo({super.key,required this.dh});

  @override
  State<Foo> createState() => _FooState();
}

class _FooState extends State<Foo> {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.red.withOpacity(0.5),
      child: ListenableBuilder(
        listenable: widget.dh,
        builder: (context, child) {
          return Column(
            children: [
              FlutterLogo(
                size: widget.dh.value * 100 + 50,
              ),
              Slider(
                value: widget.dh.value,
                onChanged: (value) {
                  setState(() {
                    widget.dh.value = value;
                  });
                },
              ),
            ],
          );
        },
      ),
    );
  }
}

继承式组件

  • 这是上面介绍的几种基础的状态管理方式

  • 但是项目中的组件一般都是多层嵌套,如果经过状态提升之后,每次都依赖这种简单的传参的方式,很可能每次创建组件都需要在构造函数里传入大量的参数

使用

  • 例子:系统现有的继承式组件
arduino 复制代码
Widget build(BuildContext context) {
  MediaQuery.of(context).size;
  return ...;
 }
  • 尝试自己实现一个InheritedWidget
  • 实现前先提一个问题:

    • 假设上面的红色方块是一个const的组件,在外面的按钮上调用setState,红色方块会不会刷新?
    • 如果这个颜色是引用的InheritedWidget里的属性呢?
dart 复制代码
// 自定义一个
class MyColor extends InheritedWidget {
  final Color color;
  const MyColor({required super.child,required this.color});

// 核心方法
  @override
  bool updateShouldNotify(covariant MyColor oldWidget) {
    print('updateShouldNotify;${oldWidget.color} -> $color');
    return true;
  }

  static MyColor? maybeOf(BuildContext context){
    return context.dependOnInheritedWidgetOfExactType<MyColor>();
  }

  static MyColor of(BuildContext context){
    return context.dependOnInheritedWidgetOfExactType<MyColor>()!;
  }

}
  • 顶层加个MyColor(继承)
scala 复制代码
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Color _color = Colors.red;

  @override
  Widget build(BuildContext context) {
    return MyColor(
      color: _color,
      child: Scaffold(
        body: Center(
            child: const Foo()
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {
              _color = Colors.black;
            });
          },
        ),
      ),
    );
  }
}
  • 底层调用
scala 复制代码
class Foo extends StatelessWidget {
  const Foo({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      color: MyColor.of(context).color,
    );
  }
}
  • 结果

原理

  • 本质就是全局维护一个Map,对map进行存入、读取。
  • 每个value(InheritedElement)有自己的更新操作,他绑定了关联的widget
  • 当 InheritedWidget 更新时,先比对数据,数据改变会通知所有依赖它的 Widget 重建

Map的创建和存入

  • 自定义一个InheritedWidget
scala 复制代码
class MyColor extends InheritedWidget {
  final Color color;
  const MyColor({required super.child,required this.color});
  
  @override
  bool updateShouldNotify(covariant MyColor oldWidget) {
     return oldWidget.color != color;
  }
}

// 使用,最外层包一个
@override
Widget build(BuildContext context) {
  return MyColor(
    color: _color,
    child: Scaffold(...),
    );
}
  • 渲染三棵树时构建出共有Map
dart 复制代码
class InheritedElement extends ProxyElement {
    
void mount(Element? parent, Object? newSlot) {
  // 节点激活时调用
  _updateInheritance();
  ...
}
void activate() {
    // 脏节点回收时
  _updateInheritance();
}

// 每个Element里面都共有一个map的引用,从父类那里获取
//持久化哈希映射:是一种不可变的数据结构,当修改数据时,不会改变原实例,而是创建一个包含修改的新实例,同时尽可能复用原实例的未变部分,
PersistentHashMap<Type, InheritedElement>? _inheritedElements;

// 存入的方法,在mount和activate时调用
  @override
  void _updateInheritance() {
    assert(_lifecycleState == _ElementLifecycle.active);
    // 获取父类的_inheritedElements
    final PersistentHashMap<Type, InheritedElement> incomingWidgets =
        _parent?._inheritedElements ?? const PersistentHashMap<Type, InheritedElement>.empty();
        // 存入当前InheritedElement,key是类型,value是本体
    _inheritedElements = incomingWidgets.put(widget.runtimeType, this);
  }
}

Map的读取以及widget绑定过程

  • 一般使用InheritedWidget
scala 复制代码
class _FooState extends State<Foo> {
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      color: MyColor.of(context).color,
    );
  }
  
static MyColor of(BuildContext context){
   return context.dependOnInheritedWidgetOfExactType<MyColor>()!;
}
  • 实际上干的事情
scala 复制代码
abstract class Element extends DiagnosticableTree implements BuildContext {
PersistentHashMap<Type, InheritedElement>? _inheritedElements;

// 读取以及绑定
@override
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
    // 向上找,有没有存入过这个类型的InheritedElement
  final InheritedElement? ancestor = _inheritedElements == null ? null : _inheritedElements![T];
  if (ancestor != null) {
      // 匹配的InheritedElement不为空,就建立依赖关系
      // 当 InheritedWidget 数据更新时,框架会通知所有依赖它的 Widget 重建
    return dependOnInheritedElement(ancestor, aspect: aspect) as T;
  }
  _hadUnsatisfiedDependencies = true;
  return null;
}
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
  _dependencies ??= HashSet<InheritedElement>();
  _dependencies!.add(ancestor);
  // 将当前 Element->this 加入 InheritedElement->ancestor 的依赖列表
  ancestor.updateDependencies(this, aspect);
  return ancestor.widget as InheritedWidget;
}
dart 复制代码
class InheritedElement extends ProxyElement {
final Map<Element, Object?> _dependents = HashMap<Element, Object?>();
@protected
void updateDependencies(Element dependent, Object? aspect) {
  setDependencies(dependent, null);
}

@protected
void setDependencies(Element dependent, Object? value) {
  _dependents[dependent] = value;
}

InheritedWidget 更新时,通知所有依赖它的 Widget 重建

less 复制代码
Color _color = Colors.red;

@override
Widget build(BuildContext context) {
  return MyColor(
    color: _color,
    child: Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Foo(),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          setState(() {
            _color = Colors.black;
          });
        },
      ), 
    ),
  );
}
scss 复制代码
// 更新
@override
void updated(InheritedWidget oldWidget) {
  if ((widget as InheritedWidget).updateShouldNotify(oldWidget)) {
    super.updated(oldWidget);
  }
}
  @override
  void update(InheritedWidget newWidget) {
    final oldWidget = widget;
    super.update(newWidget);
    // 若需要通知依赖者(updateShouldNotify 返回 true)
    if (widget.updateShouldNotify(oldWidget)) {
      notifyClients(oldWidget);
    }
  }

  // 通知所有依赖的 Element 重建
  @protected
  void notifyClients(covariant InheritedWidget oldWidget) {
    // 遍历所有依赖的 Element
    for (final Element dependent in _dependents.keys) {
      // 检查依赖是否有效
      if (dependent._active) {
        // 标记依赖的 Element 为脏节点,并调度重建
        dependent.markNeedsBuild();
      }
    }
  }

Provider

使用:

  • 外层包一个ChangeNotifierProvider
  • 传一个LogoModel构建方法
  • 内部组件可以直接通过 watch绑定 LogoModel数据
scala 复制代码
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => LogoModel(),
      child: const MaterialApp(
        title: 'Flutter Demo',
        home: MyHomePage(),
      ),
    );
  }
}
ini 复制代码
class LogoModel extends ChangeNotifier{
  bool _flipX = false;
  bool _flipY = false;
  double _size = 200.0;

  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();
  }
}
scala 复制代码
class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Logo(),
            ControlPanel(),
          ],
        ),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}
scala 复制代码
class Logo extends StatelessWidget {
  const Logo({super.key});

  @override
  Widget build(BuildContext context) {
    final model = context.watch<LogoModel>();
    return Card(
      child: Transform.flip(
        flipX: model.flipX,
        flipY: model.flipY,
        child: FlutterLogo(
          size: model.size,
        ),
      ),
    );
  }
}
less 复制代码
class ControlPanel extends StatelessWidget {
  const ControlPanel({super.key});

  @override
  Widget build(BuildContext context) {
    var model = context.watch<LogoModel>();
    return Card(
      margin: const EdgeInsets.all(32),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Text('flip X:'),
                Switch(
                    value: model.flipX,
                    onChanged: (value) {
                      model.flipX = value;
                    }),
                const Text('flip y:'),
                Switch(
                    value: model.flipY,
                    onChanged: (value) {
                      model.flipY = value;
                    }),
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Text('size:'),
                Slider(
                    min: 50,
                    max: 300,
                    value: model.size,
                    onChanged: (value) {
                      model.size = value;
                    }),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

如何在不使用三方库的情况下,手写一个Provide

原理:

  • 主要就是用到控制器和继承式组件
  • 数据和我们自己写的布局都是作为参数传递进入的

  • 读的过程通过InheritedWidget向上查找数据

  • 修改数据后,通过ListenableBuilder刷新他子类,也就是InheritedWidget

  • InheritedWidget刷新时,为什么不会导致我们的布局刷新?

    • 因为他是外层传进去的参数,他其实存在最外层stateful里面,而ListenableBuilder刷新不会导致最外层stateful刷新

代码

scala 复制代码
void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MyChangeNotifierProvider(
      create: () => LogoModel(),
      child: const MaterialApp(
        title: 'Flutter Demo',
        home: MyHomePage(),
      ),
    );
  }
}

class LogoModel extends ChangeNotifier {
  bool _flipX = false;
  bool _flipY = false;
  double _size = 200.0;

  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();
  }
}

class MyChangeNotifierProvider<T extends Listenable> extends StatefulWidget {
  final Widget child;
  final T Function() create;

  const MyChangeNotifierProvider(
      {super.key, required this.child, required this.create});

  @override
  State<MyChangeNotifierProvider> createState() =>
      _MyChangeNotifierProviderState<T>();

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

class _MyChangeNotifierProviderState<T extends Listenable>
    extends State<MyChangeNotifierProvider<T>> {
  late T model;

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

  @override
  Widget build(BuildContext context) {
    return ListenableBuilder(
        listenable: model,
        builder: (BuildContext context, Widget? child) {
          // child是外面传进来的,所以重建的时候不会rebuild
          return _MyInheritedWidget(model: model, child: widget.child);
        });
  }
}

class _MyInheritedWidget<T> extends InheritedWidget {
  final T model;

  const _MyInheritedWidget({required super.child, required this.model});

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

// 扩展BuildContext
extension Consumer on BuildContext {
  // 绑定
  T watch<T>() => MyChangeNotifierProvider.of(this, listen: true);
  // 只读一次
  T read<T>() => MyChangeNotifierProvider.of(this, listen: false);
}
  • 使用
scala 复制代码
class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});
  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Logo(),
            ControlPanel(),
          ],
        ),
      ),
    );
  }
}

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

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

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 =
        Consumer(context).watch<LogoModel>();
    return Card(
      margin: const EdgeInsets.all(32),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Text('flip X:'),
                Switch(
                    value: model.flipX,
                    onChanged: (value) {
                      model.flipX = value;
                    }),
                const Text('flip y:'),
                Switch(
                    value: model.flipY,
                    onChanged: (value) {
                      model.flipY = value;
                    }),
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Text('size:'),
                Slider(
                    min: 50,
                    max: 300,
                    value: model.size,
                    onChanged: (value) {
                      model.size = value;
                    }),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
相关推荐
一笑的小酒馆17 小时前
Android launcher3实现简单的负一屏功能
android
xuyin120418 小时前
【Android】Flow基础知识和使用
android
李新_19 小时前
基于Markwon封装Markdown组件
android·aigc·markdown
Non-existent98721 小时前
Flutter + FastAPI 30天速成计划自用并实践-第10天-组件化开发实践
android·flutter·fastapi
kirk_wang1 天前
Flutter 三方库在 OHOS 平台的适配实践:以 flutter_mailer 为例
flutter·移动开发·跨平台·arkts·鸿蒙
小白的程序空间1 天前
理解Flutter突出优点
flutter
@老蝴1 天前
MySQL数据库 - 约束和联合查询
android·数据库·mysql
ljt27249606611 天前
Compose笔记(六十一)--SelectionContainer
android·笔记·android jetpack
linweidong1 天前
实战救火型 从 500MB 降到 50MB:高频业务场景下的 iOS 内存急救与避坑指南
macos·ios·objective-c·cocoa·ios面试·nstimer·ios面经