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;
                    }),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
相关推荐
4***99743 小时前
Kotlin序列处理
android·开发语言·kotlin
t***D2643 小时前
Kotlin在服务端开发中的生态建设
android·开发语言·kotlin
BoomHe3 小时前
车载应用配置系统签名
android·android studio
路人甲ing..5 小时前
用 Android Studio 自带的模拟 Android Emulator 调试
android·java·ide·ubuntu·kotlin·android studio
路人甲ing..5 小时前
Android Studio 模拟器报错 The emulator process for AVD xxxxx has terminated.
android·java·ide·kotlin·android studio
AskHarries6 小时前
中国身份证注册美区 Apple Developer 个人账号完整教程
ios·apple
弥巷6 小时前
【Android】 View事件分发机制源码分析
android·java
心随雨下6 小时前
Flutter依赖注入使用指南
flutter
wanna7 小时前
安卓自学小笔记第一弹
android·笔记