Code前端首页关于Code前端联系我们

Flutter完整开发实践详解:全面理解state和provider

terry 2年前 (2023-09-23) 阅读数 79 #移动小程序

1.状态

1。什么是国家?

我们知道 Flutter 宇宙中的一切都是 WidgetWidgetWidget 是 ⓽ ⓽ im Widget 每个状态代表一个框架。

在此基础上,来自StatefulWidgetState帮助我们实现了通过frame进行绘制‾‾‾‾‾‾‾‾‾‾‾ ,所以每次 当 Widget 时重绘,Stat用于重新提供Widget所需的绘图信息。

2。如何实现跨框架状态共享?

这就是 Flutter 中Widget 的实现原理。我们在上一章中介绍过。这里我们要讲两个概念:

  • Flutter中的Widget在正常情况下需要将Element⓽转换为❝⓼对象才能实现绘制。
  • ElementBuildContext的实现类,Element包含♾‼Widget ,我们的方法构建.widget(BuildContext上下文){}在代码中称为Element

了解了这两个概念后,我们先看下图,在Flutter中创建一个Widget。首先,我们创建此 WidgetElement 事实上,State实现了各个帧之间的共享,将State保存在❀Element ❓Element中 每次通话时 ❓ 小部件构建(),通过testate.build(this)获得新的Widget;❙数据可以复用。 Flutter完整开发实战详解:全面理解State与Provider

国家是在哪里创建的?

如下图所示,来自StatefulWidgetcreateState是通过构造方法❓⿙❙创建的,这样保证了只要元素is 不会重新创建,并且 State 始终被重用。

同时我们来看看方法更新。当创建新的 StatefulWidget 来更新 UI 时,会再次创建新的 widget。授予_state,这个设置也导致了一个新手经常忽略的问题。 Flutter完整开发实战详解:全面理解State与Provider

首先我们看一下问题代码如下:

  • 1。在 _DemoAppState
中,我们创建了 DemoPage 并将变量 data
  • 2, Demo页 创建时,创建状态是直接传递到PageState
  • 3。在_DemoPageState中,通过文本直接显示传入的数据
  • 运行后可以看到没有问题吧? 但是当我们在4中点击setState时,发现3中的Text并没有发现任何变化,Flutter完整开发实战详解:全面理解State与Provider

    问题在于方法构造先前的StatefulElement更新:❙仅在状态StatefulElement当我们调用时在的set方法中创建设置状态运行update,我们执行_state.widget‽=_state‽_DemoPageState(this.data)传递导入的数据 时没有改变setState 在上传后执行楔子。 ♽ 方法 t.data 来自上面代码中的 3 个注释,因为 _state.widget = newWidget, State 小部件‼自然会待更新。

    3。 setState 的作用是什么?

    我们常说的setState实际上是调用了markNeedsBuild,会在内部标记元素脏了,然后 WidgetsBinding.drawFrame将在下一帧中绘制。还可以看出,setState并没有立即生效。 Flutter完整开发实战详解:全面理解State与Provider

    4。状态共享

    前面我们讲了State在Flutter中的作用和运行原理。接下来我们看一个常见的对象:InheritedWidget

      static ThemeData of(BuildContext context, { bool shadowThemeOnly = false }) {
        final _InheritedTheme inheritedTheme = context.inheritFromWidgetOfExactType(_InheritedTheme);
        if (shadowThemeOnly) {
          /// inheritedTheme 这个 Widget 内的 theme
          /// theme 内有我们需要的 ThemeData
          return inheritedTheme.theme.data;
        }
        ···
      }
    复制代码

    这里需要注意的是,inheritsFromWidgetOfExactType这个方法是做什么的呢?

    我们直接在Element中找到方法deditFromWidgetOfExactType的实现,关键代码如下:: _inheritedWidge 检查该类型是否ts InheritedElement 存在。。找到

  • 并将其添加到 _dependency,并且当前 Element 通过 ❙‶‶updateDependency 添加到 InheritedElement 来自 _dependencies 在这张地图上。
  • 返回 继承元素 中的 小部件
  •   @override
      InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
        /// 在共享 map _inheritedWidgets 中查找
        final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
        if (ancestor != null) {
          /// 返回找到的 InheritedWidget ,同时添加当前 element 处理
          return inheritFromElement(ancestor, aspect: aspect);
        }
        _hadUnsatisfiedDependencies = true;
        return null;
      }
    
      @override
      InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
        _dependencies ??= HashSet<InheritedElement>();
        _dependencies.add(ancestor);
       /// 就是将当前 element(this) 添加到  _dependents 里
       /// 也就是 InheritedElement 的 _dependents
       /// _dependents[dependent] = value;
        ancestor.updateDependencies(this, aspect);
        return ancestor.widget;
      }
    
      @override
      void notifyClients(InheritedWidget oldWidget) {
        for (Element dependent in _dependents.keys) {
          notifyDependent(oldWidget, dependent);
        }
      }
    复制代码

    这里关键是ancestor.updateDependency(this,spect);这个方法:

    我们都知道,获取

    一般需要继承BuildContext,比如Theme.of(context),而BuildContext的实现是Element所以我们调用Elementcontext.inher itFromWidgetOfExactType , 表示的元素context将被添加到_dependents♾‼‽中。

    这是什么意思?

    例如,当我们在 StatefulWidget 中调用 Theme.of(context).primaryColor 表示此 小部件。已“注册”到 _Dependents 位于 ❙InheriteDelement。

    而当InheritedWidget更新时,如下代码所示,Element中的Flutter完整开发实战详解:全面理解State与Provider

    将会‽‽― dependent– notifyDependent ,终于触发markNeedsBuild,因此当InheritedWidget更新时,‾文本.♝引用的地点也会被触发更新。 原因。 Flutter完整开发实战详解:全面理解State与Provider

    实际分析从Provider开始。

    2。提供商

    为什么存在提供商

    由于 Flutter 和 React 技术相似,因此在 Flutter 中创建它们为 flutter_reduxxflutter_dva‼ 、 fish _flutter 大部分它们非常复杂,需要对框架概念有一定的了解。

    作为国家领导官方推荐的Flutter,scoped_model,由于其设计比较简单,有时不太适合复杂的场景。

    于是在经历了诸多坎坷后,在今年的Google I/O大会之后,provider成为了Flutter官方推荐的新的状态管理方式之一。

    其特点是: 不复杂,容易理解。当代码量不大时,可以轻松地以刷新粒度组合和控制,而原来谷歌官方flutter-provide仓库的状态管理被GG宣布,provider成为了它的替代者。

    ⚠️注意“provider”比“flutter-provide”多了一个“r”。

    题外话:以前面试的时候,面试官有时会问我,“你的开源项目代码不多”。每次我都会笑笑不语,虽然代码量可以代表一些成果,但是我非常反对用代码量来衡量一个贡献的价值。这和用加班时间来衡量员工价值有什么区别?

    0。演示代码

    如以下代码所示。实现点击计数器,其中: MultiProvider 用于:

    • _ProviderPageState 以提供多个 支持提供程序❀
    • 计数器 通过 消费者获得 位于 CountWidget♿‾ erPage 同时 AppBar 和 处于状态 文本 位于 显示CountWidget
    class _ProviderPageState extends State<ProviderPage> {
      @override
      Widget build(BuildContext context) {
        return MultiProvider(
          providers: [
            ChangeNotifierProvider(builder: (_) => ProviderModel()),
          ],
          child: Scaffold(
            appBar: AppBar(
              title: LayoutBuilder(
                builder: (BuildContext context, BoxConstraints constraints) {
                  var counter =  Provider.of<ProviderModel>(context);
                  return new Text("Provider ${counter.count.toString()}");
                },
              )
            ),
            body: CountWidget(),
          ),
        );
      }
    }
    
    class CountWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Consumer<ProviderModel>(builder: (context, counter, _) {
          return new Column(
            children: <Widget>[
              new Expanded(child: new Center(child: new Text(counter.count.toString()))),
              new Center(
                child: new FlatButton(
                    onPressed: () {
                      counter.add();
                    },
                    color: Colors.blue,
                    child: new Text("+")),
              )
            ],
          );
        });
      }
    }
    
    class ProviderModel extends ChangeNotifier {
      int _count = 0;
    
      int get count => _count;
    
      void add() {
        _count++;
        notifyListeners();
      }
    }
    复制代码

    所以在上面的代码中,我们将ChangeNotifier(ProviderModel)与ChangeNotifierProvider结合起来,实现共享;我们使用提供者。消费者获取共享状态计数器;通过调用 ChangeNotifier notifyListeners(); 触发更新。

    以下是一些见解:

    • 1。内部 DelegateWidget Provider
      @override
      InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
        /// 在共享 map _inheritedWidgets 中查找
        final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
        if (ancestor != null) {
          /// 返回找到的 InheritedWidget ,同时添加当前 element 处理
          return inheritFromElement(ancestor, aspect: aspect);
        }
        _hadUnsatisfiedDependencies = true;
        return null;
      }
    
      @override
      InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
        _dependencies ??= HashSet<InheritedElement>();
        _dependencies.add(ancestor);
       /// 就是将当前 element(this) 添加到  _dependents 里
       /// 也就是 InheritedElement 的 _dependents
       /// _dependents[dependent] = value;
        ancestor.updateDependencies(this, aspect);
        return ancestor.widget;
      }
    
      @override
      void notifyClients(InheritedWidget oldWidget) {
        for (Element dependent in _dependents.keys) {
          notifyDependent(oldWidget, dependent);
        }
      }
    复制代码

    ,因此可以更新

  • 2。状态共享是使用此 InheritedWidgetInheritedProvider 实现的。
  • 3。巧妙利用MultiProviderConsumer包实现对组合和刷新的详细控制。
  • 然后我们分析

    1并委托

    。由于这是状态管理,因此必须调用 StatefulWidgetsetState

    Provider中,对StatefulWidget的一系列生命周期管理和更新都是通过上述不同的代理完成的ChangeNotifierProvider

    大致经历了这个过程:

    设置为 ChangeNotifierProvider⿽‽change‽ 并执行♾‼️。 addListener 添加监听器 监听器
  • 监听器调用方法StateDelegateStateSetter,该方法调用setState的方法

  • 当我们运行 ChangeNotifer notifyListeners
  • Flutter完整开发实战详解:全面理解State与Provider

    MultiProvider我们使用的允许我们组合多个提供商。。如下代码所示,传入的Providers会按照倒序排列,最终组合成一个嵌套的widget树,方便我们添加多个Provider: :通过Delegate回调不同的生命周期如Disposer,也有助于我们外部的二次处理,减少外部的嵌套使用‼️‼️。

    2。共享 InheritedProvider

    的状态肯定需要 InheritedWidgetInheritedProvider 是 ♾id继承 , 的所有实现Provider 均使用 嵌套InheritedProvider.在build方法中实现的共享。

    3。 Consumer

    ConsumerProvider中比较有趣的东西。它本身StatelessWidget,刚刚通过程序集♽提供者。‼❀(上下文)‼值

    由用户共享InheritedWidget

      final Widget Function(BuildContext context, T value, Widget child) builder;
    
     @override
      Widget build(BuildContext context) {
        return builder(
          context,
          Provider.of<T>(context),
          child,
        );
      }
    复制代码

    那么我们可以直接使用Provider.of(context)来代替Consumer。没关系?

    当然可以,但是你还记得我们之前介绍InheritedWidget时说过的话:

    即将推出的❀‼️呈现了这个上下文Widget

    的 元素

    已“注册”在 继承元素 中的 _dependents

    消费者作为独立的StatelessWidget是一个优势

    T提供‾‾。在上下文中传递消费者本人。 在本例中,我们使用 Consumer,我们需要使用 Provider.value 进行嵌套,并且 ♾‼‼❙ 已更新。 ,它不会更新整个页面,而是只更新 Consumer 这个 StatelessWidget

    所以消费者深思熟虑地封装了“注册逻辑”上下文获取‽‽到‽‽‽❓状态更改。 ,需要更新技巧。

    该库还提供组合 Consumer2 ~ Consumer6。感受一下:

    
      @override
      Widget build(BuildContext context) {
        return builder(
          context,
          Provider.of<A>(context),
          Provider.of<B>(context),
          Provider.of<C>(context),
          Provider.of<D>(context),
          Provider.of<E>(context),
          Provider.of<F>(context),
          child,
        );
    复制代码

    相信这样的设置,对于使用过BLoC模式的同学来说一定会感觉很贴心。正常用作 BLoC 时,每个 StreamBuilder snapShot 仅支持一种类型。当有多个时,要么将多个状态合并为一个实体,要么需要StreamBuilder的多次嵌套。

    当然,如果你想直接使用LayoutBuilderProvider.of(context),也是可以的:❙

  • ListenableProvider value Provider.of(context) FutureProviderStreamProvider还有更多
      @override
      Widget build(BuildContext context) {
        var tree = child;
        for (final provider in providers.reversed) {
          tree = provider.cloneWithChild(tree);
        }
        return tree;
      }
    
      /// Clones the current provider with a new [child].
      /// Note for implementers: all other values, including [Key] must be
      /// preserved.
      @override
      MultiProvider cloneWithChild(Widget child) {
        return MultiProvider(
          key: key,
          providers: providers,
          child: child,
        );
      }
    复制代码

    还有更多

      @override
      Widget build(BuildContext context) {
        var tree = child;
        for (final provider in providers.reversed) {
          tree = provider.cloneWithChild(tree);
        }
        return tree;
      }
    
      /// Clones the current provider with a new [child].
      /// Note for implementers: all other values, including [Key] must be
      /// preserved.
      @override
      MultiProvider cloneWithChild(Widget child) {
        return MultiProvider(
          key: key,
          providers: providers,
          child: child,
        );
      }
    复制代码

    整个提供商'你可以看到 Provider设计更接近Flutter原生功能,设计也更好理解并考虑到性能等问题。 ?商业转载请联系作者获取授权。非商业转载请注明出处。

  • 版权声明

    本文仅代表作者观点,不代表Code前端网立场。
    本文系作者Code前端网发表,如需转载,请注明页面地址。

    发表评论:

    ◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

    热门