手写一个Flutter State Widget,来让你彻底理解State的来龙去脉

简介: 手写一个Flutter State Widget,来让你彻底理解State的来龙去脉

往期相关内容


前言


在上期,我们手写了一个Widget的实现,并接触到了Element,经过一系列的分析,我们对Widget和Element的认识更进一步,那么这期我们就来深入理解下State,相信大家在开发过程中,总会用到StatefulWidget,那么官方为什么设计一个含有State的Widget?State生命周期是怎么来的?为什么State可以更新UI?带着一些疑问,我们不直接分析源码,而是手写一个带有State的Widget怎么样?我们来做一个带State的Widget,让它有生命周期和更新UI的能力。

本次主要内容


  • 宏观看state是什么,微观看State
  • State类继承关系图
  • 手写一个带State的Widget

宏观看state是什么,微观看State


image.png

从宏观来看,flutter的UI是声明式的,那为什么是声明式?这就要从Win32到Web再到Android和Ios说起,他们都是命令式的编程风格,如下:

//android
TextView tv = TextView()
tv.setText("text")

当UI发生变化的时候,你必须调用setText来实现,但flutter恰相反,它为了减轻开发人员的负担,让开发人员只关心当前应用的状态,并交给框架自动将状态通过函数渲染在UI上,那么这样做有什么好处呢?

  • 开发人员只关心状态的变化,从架构上做到了UI和数据的分离
  • 更深入的讲,其实flutter 真实的UI对象是RenderObjects,Widget是不变的,每次刷新UI都会构建新的子Widget树,并通过Element过滤,最终RenderObject只是很小的改动,提高了渲染的效率。

那么有什么缺点吗?

  • 不合理的状态管理,导致整个页面的频繁build
  • 在Widget树中加入了状态的计算,会导致状态管理的混乱,不统一

最理想的就是如上图的公式:UI= f(state) 举个例子:

class TestState extends StatefulWidget {
  @override
  _TestState createState() => _TestState();
}
class _TestState extends State<TestState> {
  FunState _funState = FunState();
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        children: [
          Text(_funState.state + "state"), /// 不推荐
          Text(_funState.getState()) /// 推荐写法,f(state) 
        ],
      ),
    );
  }
}
class FunState {
  String state = "state";
  getState() {
    return state + "Test";
  }
}

看到了吧,我们不推荐你这样哦

Text(_funState.state + "state"), /// 不推荐
复制代码

这就是我要说的宏观state,我们简单做个定义:state其实就是反应出当前UI的状态。那么微观呢?其实就是StatefulWidget的State,都知道每个StatefulWidget会对应一个State,上期我们也学习了Widget,了解到Widget实际上是通过Element来展示UI的,那么State到底是什么角色,有什么作用呢?或者说,为什么google要这么设计呢?让我们来慢慢揭晓答案,并最终总结一下。

State类继承关系图


image.png

像我常用的Form,FormField,Overlay,Scaffold Widget,它们都会对应一个自己的State,当然也有更深一层的继承关系如AnimatedWidgetBaseState,但它的子类都是私有的。以及其他的State,通过类的继承关系,大致了解到,State类不需要特别深入的继承关系,比Widget和Element都稍微简单一些,flutter在设计之初,就一致贯穿一个设计思想就是组合大于继承,所以这也是整个UI框架的特点,也是类图都很简单的主要原因。

手写一个带State的Widget


我们还是上期的套路,继承最底层的Widget来实现,这次加一个State,来伪装成StatefulWidget,来吧。

class StateWidget extends Widget{
  /// 构造函数
  const StateWidget({ Key key }) : super(key: key);
  @override
  Element createElement() {
    // TODO: implement createElement
    throw UnimplementedError();
  }
}

创建StateWidget类,继承自Widget,它让我们实现一个Element,那我们就再创建一个Element,这次我们用ComponentElement,上期我们使用过Element了,实现起来较麻烦,这期我们要了解的是State对吧,所以我们继承ComponentElement来快速的实现并理解State

class StateWidget extends Widget{
  /// 构造函数
  const StateWidget({ Key key }) : super(key: key);
  @override
  Element createElement() {
    return StateElement(this);
  }
}
class StateElement extends ComponentElement{
  StateElement(Widget widget) : super(widget);
  @override
  Widget build() {
  }
}

我们印象中State有很多属性和函数,都有那些呢?请看图

image.png


那我们就模仿一下,把这些函数定义一下,代码如下

abstract class States<T extends StateWidget> {
  T get widget => _widget;
  T _widget;
  BuildContext get context => _element;
  StateElement _element;
  @protected
  @mustCallSuper
  void initState() {}
  @protected
  @mustCallSuper
  void didUpdateWidget(covariant T oldWidget) {}
  @protected
  @mustCallSuper
  void reassemble() {}
  @protected
  @mustCallSuper
  void setState(VoidCallback fn) {}
  @protected
  @mustCallSuper
  void deactivate() {}
  @protected
  @mustCallSuper
  void dispose() {}
  @protected
  Widget build(BuildContext context);
  @protected
  @mustCallSuper
  void didChangeDependencies() { }
}

好了我们的States就这样被定义完了,protected关键字跟java的作用域应该是一样的,mustCallSuper是让子类实现必须调用super.当前函数,当然它还有debugFillProperties等debug相关的函数,这些我们先不关心,我们先把最核心的问题搞定,接下来,如何将States接入Widget呢?想想我们之前怎么用的?

@override
  _TestState createState() => _TestState();

对的,Widget有个createState函数,我们也来加一下,如下:

abstract class StateWidget extends Widget {
  /// 构造函数
  const StateWidget({Key key}) : super(key: key);
  @override
  Element createElement() {
    return StateElement(this);
  }
  @protected
  @factory
  States createState();
}

factory的注释含义: 用于注释实例或静态方法。 表示该方法要么是抽象的,要么必须返回新分配的对象或“null”。另外,每个实现或覆盖该方法都是隐式的使用相同的注释进行注释。

通过实现,我们发现State其实同时有Widget和Element的引用的,Widget已经完成了,那再来看看Element如何做呢?我们再来看下Element的代码

class StateElement extends ComponentElement {
  ///这里将之前的Widget改为StateWidget,免得强转
  StateElement(StateWidget widget) : super(widget);
  @override
  Widget build() {
  }
}

继承自ComponentElement,覆盖build函数,而State里面恰巧有个抽象函数build,那么肯定是这里了

class StateElement extends ComponentElement {
  States<StateWidget> get state => _state;
  States<StateWidget> _state;
  StateElement(StateWidget widget) : super(widget);
  @override
  Widget build() {
    return _state.build(this);
  }
}

在Element里缓存一下State,并在build中调用_state.build(this), 这个this就是我们熟悉的BuildContext,而BuildContext的实例就是当前Element对象。现在你会发现,_state并没有赋值对吧,它是widget里的createState函数返回的,那我们什么时候调用合适呢?为了避免它多次createState,在构造函数里是不是更合适呢?放进去如下

class StateElement extends ComponentElement {
  States<StateWidget> get state => _state;
  States<StateWidget> _state;
  StateElement(StateWidget widget)
        ///创建State
      : _state = widget.createState(),
        super(widget){
    ///断言判断
    assert(_state._element == null);
    ///给State里的element赋值,也就是你在State里获取的context
    _state._element = this;
    assert(_state._widget == null);
    ///state里的widget赋值
    _state._widget = widget;
  }
  @override
  Widget build() {
    return _state.build(this);
  }
}

在构造函数里已经将State里的element,widget统统赋值了,紧接着就是State的initState()函数,这是我们经常用的初始化函数,那么它是在Element什么时候被调用的呢?或者说什么时候调用比较合适,首先一点,它肯定只调用一次,不可能初始化两次把,这样不太合理,有人说放构造里行吗?我们再来看看Element的生命周期

image.png

系统调用createElement后,当Element真正被挂载到树中的时候,才会调用mount,如果Element根本没有挂载到UI上,我们是不是就没必要初始化呢?那放在构造合适吗?不合适对吧,所以实现如下:

class StateElement extends ComponentElement {
  States<StateWidget> get state => _state;
  States<StateWidget> _state;
  bool isNeedInit;
  StateElement(StateWidget widget)
        ///创建State
      : _state = widget.createState(),
        super(widget){
    ///断言判断
    assert(_state._element == null);
    ///给State里的element赋值,也就是你在State里获取的context
    _state._element = this;
    assert(_state._widget == null);
    ///state里的widget赋值
    _state._widget = widget;
  }
  @override
  void mount(Element parent, newSlot) {
    super.mount(parent, newSlot);
    assert(isNeedInit == null);
    _state.initState();
    isNeedInit = false;
  }
  @override
  Widget build() {
    return _state.build(this);
  }
}

在mount函数中调用initState函数,并通过isNeedInit变量控制只调用一次。紧接着看didChangeDependencies函数,为什么说它呢?当此State对象的依赖项更改时调用,还有就是在initState后调用,官方解释:子类很少重写此方法,因为框架总是在依赖项更改后调用build。一些子类确实重写了此方法,因为当它们的依存关系发生变化时,它们需要做一些昂贵的工作(例如,网络获取),并且对于每个构建而言,所做的工作都太昂贵了。

详细理解请看大佬分析:www.jianshu.com/p/9cb6c57b7…

其实说白了就是Widget类型发生变化时,就会触发,State里的didChangeDependencies触发需要满足两个条件,一个就是第一次加载,它认为Widget类型从null转换为具体的Widget,再一个就是Element的Widget确实有了变化,系统调用Element的didChangeDependencies,这个时候才有必要执行State的didChangeDependencies,代码实现如下:

class StateElement extends ComponentElement {
  States<StateWidget> get state => _state;
  States<StateWidget> _state;
  bool isNeedInit;
  bool _didChangeDependencies = false;
  StateElement(StateWidget widget)
        ///创建State
      : _state = widget.createState(),
        super(widget){
    ///断言判断
    assert(_state._element == null);
    ///给State里的element赋值,也就是你在State里获取的context
    _state._element = this;
    assert(_state._widget == null);
    ///state里的widget赋值
    _state._widget = widget;
  }
  @override
  void mount(Element parent, newSlot) {
    super.mount(parent, newSlot);
    assert(isNeedInit == null);
    _state.initState();
    ///第一次加载的时候,Widget从Null变为具体的Widget
    _state.didChangeDependencies();
    isNeedInit = false;
  }
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    ///Element的Widget确实有了变化
    _didChangeDependencies = true;
  }
  @override
  void performRebuild() {
    ///这个时候才有必要执行State的didChangeDependencies
    if (_didChangeDependencies) {
      _state.didChangeDependencies();
      _didChangeDependencies = false;
    }
    super.performRebuild();
  }
  @override
  Widget build() {
    return _state.build(this);
  }
}

在element的performRebuild函数中执行State的didChangeDependencies,条件就是在系统执行了Element的didChangeDependencies函数。

再往下,我们看下reassemble函数,这个函数是干嘛的呢?此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在Release模式下永远不会被调用。这个我们不必关心太多,直接在Element里调用即可,实现如下:

@override
  void reassemble() {
    _state.reassemble();
    super.reassemble();
  }

再来看下didUpdateWidget函数,在widget重新构建时,Flutter framework会调用Widget.canUpdate来检测Widget树中同一位置的新旧节点,然后决定是否需要更新,如果Widget.canUpdate返回true则会调用此回调。正如之前所述,Widget.canUpdate会在新旧widget的key和runtimeType同时相等时会返回true,也就是说在在新旧widget的key和runtimeType同时相等时didUpdateWidget()就会被调用,但你发现Element里面没有这个函数,它只有update函数。然后我看了下StatefulElement的实现就是在这里调用的didUpdateWidget,那我们来实现下,看看都什么逻辑

class StateElement extends ComponentElement {
  States<StateWidget> get state => _state;
  States<StateWidget> _state;
  bool isNeedInit;
  bool _didChangeDependencies = false;
  StateElement(StateWidget widget)
      ///创建State
      : _state = widget.createState(),
        super(widget) {
    ///断言判断
    assert(_state._element == null);
    ///给State里的element赋值,也就是你在State里获取的context
    _state._element = this;
    assert(_state._widget == null);
    ///state里的widget赋值
    _state._widget = widget;
  }
  @override
  void mount(Element parent, newSlot) {
    super.mount(parent, newSlot);
    assert(isNeedInit == null);
    _state.initState();
    ///第一次加载的时候,Widget从Null变为具体的Widget
    _state.didChangeDependencies();
    isNeedInit = false;
  }
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    ///Element的Widget确实有了变化
    _didChangeDependencies = true;
  }
  @override
  void performRebuild() {
    ///这个时候才有必要执行State的didChangeDependencies
    if (_didChangeDependencies) {
      _state.didChangeDependencies();
      _didChangeDependencies = false;
    }
    super.performRebuild();
  }
  @override
  void reassemble() {
    _state.reassemble();
    super.reassemble();
  }
  @override
  void update(Widget newWidget) {
    super.update(newWidget);
    assert(widget == newWidget);
    /// 先拿到旧的widget
    final StateWidget oldWidget = _state._widget;
    /// 强制将状态至为可更新
    markNeedsBuild();
    /// 将State的Widget更新为新的Widget
    _state._widget = widget as StateWidget;
    /// 回调_state.didUpdateWidget
    _state.didUpdateWidget(oldWidget);
    /// 调用rebuild函数,最终调用performRebuild,更新Element
    rebuild();
  }
  @override
  Widget build() {
    return _state.build(this);
  }
}

update函数如上实现,你会发现ditUpdateWidget参数是旧的Widget,这点使用的时候要注意哦。update函数结束了,来看下deactivate,当State对象从树中被移除时,会调用此回调。在一些场景下,Flutter framework会将State对象重新插到树中,如包含此State对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey来实现)。如果移除后没有重新插入到树中则紧接着会调用dispose()方法。看实现很简单:

@override
  void deactivate() {
    /// 不需要特殊处理
    _state.deactivate();
    super.deactivate();
  }

再来看下dispose函数,dispose意义上是释放,那Element是什么时候释放的呢?那肯定是unmount函数了,所以不言而喻,实现如下:

@override
  void unmount() {
    super.unmount();
    _state.dispose();
    /// 至null来释放调引用
    _state._element = null;
    _state = null;
  }

压轴的函数setState来了,几乎State所有的生命周期函数里,没有几个是有实现的,而setState需要实现,它是Widget能够重建的核心,直接上代码分析哈:

abstract class States<T extends StateWidget> {
  T get widget => _widget;
  T _widget;
  BuildContext get context => _element;
  StateElement _element;
  @protected
  @mustCallSuper
  void initState() {}
  @protected
  @mustCallSuper
  void didUpdateWidget(covariant T oldWidget) {}
  @protected
  @mustCallSuper
  void reassemble() {}
  @protected
  @mustCallSuper
  void setState(VoidCallback fn) {
    assert(fn != null);
    ///...省略了状态判断
    final dynamic result = fn() as dynamic;
    assert(() {
      if (result is Future) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('setState() callback argument returned a Future.'),
          ErrorDescription(
              'The setState() method on $this was called with a closure or method that '
                  'returned a Future. Maybe it is marked as "async".'
          ),
          ErrorHint(
              'Instead of performing asynchronous work inside a call to setState(), first '
                  'execute the work (without updating the widget state), and then synchronously '
                  'update the state inside a call to setState().'
          ),
        ]);
      }
      // We ignore other types of return values so that you can do things like:
      //   setState(() => x = 3);
      return true;
    }());
    /// 最重要的一句,markNeedsBuild,让Element处与可更新状态,等下framework层主动刷新。
    _element.markNeedsBuild();
  }
  @protected
  @mustCallSuper
  void deactivate() {}
  @protected
  @mustCallSuper
  void dispose() {}
  @protected
  Widget build(BuildContext context);
  @protected
  @mustCallSuper
  void didChangeDependencies() {}
}

它首先判断了fn是否为空,然后加入了state状态的判断,这里我省略了,想看的可以直接看State源码哦。往下就是对fn函数的Future情况处理,最最后调用了_element.markNeedsBuild();对哦,这其实才是我们Widget重新构建的关键,它其实就是改了一个标志位_dirty,设置true后,framework层就知道它要更新,会执行响应的更新,由于实际的更新是异步的,所以你可以在setState函数的前后或者函数中,都可以更新状态。

好了终于实现完了,是骡子是马,总要拉出来溜溜,我们自己实现的States能用吗?来实验一发。代码如下:

class TestStateWidget extends StateWidget {
  @override
  States<StateWidget> createState() {
    return TestStates();
  }
}
class TestStates extends States<TestStateWidget> {
  String data;
  int num = 0;
  @override
  void initState() {
    data = "123";
    super.initState();
  }
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(8),
      child: Column(
        children: [
          Text(data),
          MaterialButton(
            onPressed: () {
              setState(() {
                data = "456${num++}";
              });
            },
            child: Text("更新"),
          )
        ],
      ),
    );
  }
}

放入main.dart中:

@override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Column(
        children: [
          // Center(
          //   // Center is a layout widget. It takes a single child and positions it
          //   // in the middle of the parent.
          //   child: isShow ? const TestWidget() : Container(),
          // ),
          // MaterialButton(
          //   onPressed: () {
          //     showialog(context);
          //   },
          //   child: Text('showDialog'),
          // ),
          // Center(
          //   // Center is a layout widget. It takes a single child and positions it
          //   // in the middle of the parent.
          //   child: isShow
          //       ? TestWidget(
          //           key: key,
          //         )
          //       : Container(
          //           padding: EdgeInsets.all(9),
          //           child: TestWidget(
          //             key: key,
          //           ),
          //         ),
          // ),
          TestStateWidget()
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            isShow = !isShow;
          });
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

运行效果:

image.png

点击更新

image.png

再点击

image.png

没毛病把,完成了,效果很好,实现了一套带State的Widget对吧。下面我们来个总结。

总结


生命周期总结一张图

39a62c8bbfff42248d47192cae36b60a_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.jpg

  • State的状态都是来源于Element,说白了就是Element的中介,或者叫委托更合适
  • Context可以是Element,但State不能,因为他们不是继承关系。
  • 掌握Element的重要性又出来了,因为State的功能受限于Element。
  • 在MVVM架构中,State是不是充当着VM的角色?我觉得可以这么认为。

等等把,好了,讲到这里该告一段落了,期待你的认可,点个赞就行。感谢。

目录
相关文章
|
7天前
|
容器
Flutter Widget 解析
Flutter Widget 解析
|
7天前
|
存储 容器
Flutter 有状态Widget 和 无状态Widget
Flutter 有状态Widget 和 无状态Widget
|
1月前
深入理解Flutter鸿蒙next版本 中的Widget继承:使用extends获取数据与父类约束
本文详细介绍了Flutter中如何通过继承其他Widget来创建自定义组件。首先解释了Widget继承的基本概念,包括StatelessWidget和StatefulWidget的区别。接着通过具体示例展示了如何继承StatelessWidget和StatefulWidget,并在子类中访问父类的build方法和状态。最后,结合多个自定义Widget展示了如何在实际应用中灵活使用继承和组合来构建复杂的UI。
79 8
|
1月前
|
容器
flutter&鸿蒙next 使用 InheritedWidget 实现跨 Widget 传递状态
在 Flutter 中,状态管理至关重要。本文详细介绍了如何使用 InheritedWidget 实现跨 Widget 的状态传递。InheritedWidget 允许数据在 Widget 树中向下传递,适用于多层嵌套的场景。通过一个简单的计数器示例,展示了如何创建和使用 InheritedWidget,包括其基础概念、工作原理及代码实现。虽然 InheritedWidget 较底层,但它是许多高级状态管理解决方案的基础。
107 2
|
2月前
|
容器
flutter:第一个flutter&Widget的使用 (二)
本文介绍了Flutter框架下的基本组件及其用法,包括简单的 Stateless Widget 如文本和按钮,以及更复杂的 StatefulWidget 示例。详细解释了如何使用 `context` 获取祖先小部件的信息,并展示了 `MaterialApp` 的属性及用途。此外,还探讨了 `StatefulWidget` 与 `StatelessWidget` 的区别,以及 `AppBar` 的常见属性配置方法。适合Flutter初学者参考学习。
|
1月前
|
Dart JavaScript 前端开发
Flutter 的 Widget 概述与常用 Widgets 与鸿蒙 Next 的对比
Flutter 是 Google 开发的开源 UI 框架,用于快速构建高性能的移动、Web 和桌面应用。Flutter 通过 Widget 构建 UI,每个 UI 元素都是 Widget,包括文本、按钮、图片等。Widget 不仅描述外观,还描述行为,是不可变的。常见的 Widget 包括结构型(Container、Column、Row)、呈现型(Text、Image)、交互型(ElevatedButton)和状态管理型(StatefulWidget)。Flutter 与鸿蒙 Next 在组件化架构、开发语言、布局系统、性能和跨平台支持方面各有优势
77 0
|
5月前
Flutter-底部弹出框(Widget层级)
文章描述了如何在Flutter中使用DraggableScrollableSheet创建一个底部弹出框,同时保持其可手势滑动关闭。作者遇到问题并提出对原控件进行扩展,以支持头部和列表布局的滑动关闭功能。
201 0
|
6月前
Flutter StreamBuilder 实现局部刷新 Widget
Flutter StreamBuilder 实现局部刷新 Widget
53 0
|
7月前
|
Android开发
Flutter完整开发实战详解(六、 深入Widget原理),2024百度Android岗面试真题收录解析
Flutter完整开发实战详解(六、 深入Widget原理),2024百度Android岗面试真题收录解析
|
7月前
|
开发框架 前端开发 搜索推荐
【Flutter前端技术开发专栏】Flutter中的自定义Widget与渲染流程
【4月更文挑战第30天】探索Flutter的自定义Widget与渲染流程。自定义Widget是实现复杂UI设计的关键,优点在于个性化设计、功能扩展和代码复用,但也面临性能优化和复杂性管理的挑战。创建步骤包括设计结构、定义Widget类、实现构建逻辑和处理交互。Flutter渲染流程涉及渲染对象树、布局、绘制和合成阶段。实践案例展示如何创建带渐变背景和阴影的自定义按钮。了解这些知识能提升应用体验并应对开发挑战。查阅官方文档以深入学习。
88 0
【Flutter前端技术开发专栏】Flutter中的自定义Widget与渲染流程