Flutter - Widget-Context-Stage

读前须知:此篇文章基本上是Widget – State – Context – InheritedWidget的翻译并且删减了部分我个人觉得没有意义的文字,保留下来的部分也不会逐字逐句精确翻译,所以其实强烈推荐阅读英文原文。

以下文章里面除了第一张思维导图是本人所作之外,凡是出现的示例代码和图片都是上面提到的英文文章里面的。本人在这里对此表示感激和愧疚,如果涉及到侵权,本人会立即删掉整篇文章。

All the example codes and images used in this article are from Widget – State – Context – InheritedWidget excepting the first one. If this is of tort, I will delete this article immediately.

正文开始:

Widget, State 和Context是每一个flutter开发者必须要完全理解的概念,但是具体来说要理解哪些知识呢?这篇文章会就以下几个知识点进行讲解:

1: Stateful和Stateless widget的差别

2:什么是Context

3:  什么是State以及怎么使用

4:一个context和他的state之间的关系

5:InheritedWidget以及在在Widget树里面怎么传递信息

6:rebuild的概念

接下来文章会按照以下结构去展开:

《Flutter - Widget-Context-Stage》

PS:由于此篇的篇幅很长,此思维导图里面的第二部分(左边的’怎样获取State‘)的篇幅也会很长,所以第二部分会放到下一篇文章讲解。

一:基本概念解释

1:什么是Widget

Widget即组件。在flutter里面几乎万物都是Widget,跟我们常说的component是同一个概念。

2: 什么是Widget tree

Widget按照树形结构组合的产物就是Widget tree。这个Widget tree的每个节点上又是一个Widget。包含其他组件的组件叫做父组件,被其他组件包含的组件叫做子组件。比如下面一段代码:

@override
Widget build(BuildContext){
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ),
    );
}

上面的一段代码,如果我们用图像表示它的widget tree的话就如下图所示:
《Flutter - Widget-Context-Stage》

3:什么是Context
一个context标识了一个widget在widget tree的结构中是在哪个地方被build的。简而言之就是一个context标明了一个widget是挂载在widget tree的那个节点的。

一个context只属于一个widget。
假如一个widget A有子组件,那么widget A的context会变成子组件的父context。

以上文提到的widget tree为例子,假如我们画出它的context,如下图所示(一个颜色代表一个context):
《Flutter - Widget-Context-Stage》

Context Visibility(上下文可见性)

只在其自己的context或者在其父context可见。
从以上描述,我们可以轻易地找到一个widget的父widget,例如:

Scaffold > Center > Column > Text:

context.ancestorWidgetOfExtractType(Scaffold) => 会找到第一个沿着Text的context一路向上而遇到的第一个Scaffold.

从一个父组件,也能找到子组件,但是不建议这么做(稍后会讨论到)。

4:Widget的分类
在Flutter里面有2类widget:

  • Stateless Widget
  • Stateful Widget

从字面意思上可以理解Stateless Widget就是无状态的组件,Stateful Widget是有状态的组件,接下来我们对二者做更具体的讲解。
Stateless Widget
有些组件只依赖于他们自己的配置信息,这些配置信息一般是由他们的父组件在build他们的时候提供。
换句话说,这些组件一旦创建之后,就不用担心任何变化。
这类型的组件就是Stateless Widget.
典型的例子比如Text, Row, Column, Container等,对于这些组件来说在build的时候,我们只需要传一些参数给他们就好。
一个Stateless Widget只能被build一次,意思就是一旦build,不会因为任何的事件或者用户行为而重新build。

Stateless Widget lifecycle
下面是一个典型的Stateless Widget的代码结构。
如我们所见,我们传递一些阐述给它的构造函数,但是请记住,这些参数不会再之后被改变了,所以一般你也会看到这些参数是被定义为final的。

class MyStatelessWidget extends StatelessWidget {

    MyStatelessWidget({
        Key key,
        this.parameter,
    }): super(key:key);

    final parameter;

    @override
    Widget build(BuildContext context){
        return new ...
    }
}

虽然有另一个方法(createElement)可以被overridden,但是一般没人会用到它。唯一一个需要被override的方法就是build().
Stateless widget的生命周期是直接而简单的,如下所示:

  • 初始化
  • 通过build()方法渲染

Stateful Widget

一些其他的组件所拥有的数据会在组件生命周期内产生变化,这些数据就变成了dynamic(动态的)的。

这些被组件拥有的会在组件的生命周期内改变的数据的列表,我们叫做State

而拥有以上特点的组件,我们就叫做Stateful Widget

Stateful Widget的例子就好比一个用户可以选择的Checkboxes的列表或者一个会根据某种条件而disabled的Button。

5: 什么是State
一个State定义了一个StatefulWidget的“行为”部分。
State包含了以下旨在与一个组件交互的:

  • 行为(behaviour)
  • UI布局(layout)

任何对State的改变都会导致这个组件的rebuild。

6:State和Context之间的关系
对Stateful Widgets而言,一个State是和一个Context是绑定的,而且这种绑定关系是永久的,一个State永远不会改变他的Context。

即使一个组件的Context在Widget tree上发生了移动,这个State还是会依然和那个Context绑定在一起。

非常重要的知识点:

因为一个State对象和一个Context是绑定的,这就意味着这个State对象不能从其他的Context下直接被获取到(这一点之后会讨论到)

7:StatefulWidget的生命周期(lifecycle)
前面已经介绍和很多StatefulWidget的相关概念和基础知识,接下来我们来了解一下StatefulWidget的生命周期,这里不会介绍全部的生命周期,先只挑几个重要的,与本篇文章的主旨相关的几个来讲。先看下下面一个StatefulWidget的例子:

class MyStatefulWidget extends StatefulWidget {

    MyStatefulWidget({
        Key key,
        this.parameter,
    }): super(key: key);
    
    final parameter;
    
    @override
    _MyStatefulWidgetState createState() => new _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {

    @override
    void initState(){
        super.initState();
        
        // Additional initialization of the State
    }
    
    @override
    void didChangeDependencies(){
        super.didChangeDependencies();
        
        // Additional code
    }
    
    @override
    void dispose(){
        // Additional disposal code
        
        super.dispose();
    }
    
    @override
    Widget build(BuildContext context){
        return new ...
    }
}

下面的这个图展示了(一个简化的版本)一个StatefuleWidget在创建的时候,内部的一些列行为。在这个图的右边你可以看到一个State对象的内部状态变化以及最右边的Context是在什么时候和State产出联系而因此变为可用的。
《Flutter - Widget-Context-Stage》

initState()
initState()是在构造函数之后的第一个被调用的生命周期方法。当你需要再初始化阶段做一个额外的操作的时候,你需要override它。典型的在initState()方法里额外的操作比如动画,或者某些数据准备。假如你override initState(),记得调用super.initState()并且这行代码要放在initState()方法体的最前面。意思就是你得让super.initState()执行了之后再去执行你额外的初始化工作。

在这个阶段,一个context是存在的,但是你并不能真正地使用它,因为这时候context和state还没有完成绑定。

一旦initState()执行完毕,State对象就初始化好了,context也就可以被使用了。

initState()在整个生命周期内只会被调用一次

didChangeDependencies()
didChangeDependencies()是在生命周期里第二个被调用的方法。

这个阶段,context已经可以被使用了。

假如你的组件是链接到了InheritedWidget,根据context你需要初始化一些listeners(监听者),通常你需要override这个方法。

注意,如果某个组件是链接了InheritedWidget,那么这个组件每次重建(rebuild),didChangeDependencies()都会被调用。

假如你要override这个方法,你应该首先调用super.didChangeDependencies().

build()
build()跟在didChangeDependencies()(和didUpdateWidget())之后被调用。

这个方法就是用来构建你的组件的。

每一次State对象发生变化(或者InheritedWidget需要通知它的注册者)时,build()方法都会被调用。

通常,我们通过调用setState((){…})来改变State对象,强制build()被调用,从而重新build我们的widget。

dispose()
dispose()在这个组件被销毁的时候被调用。

一般我们override这个方法,可以在组件被销毁的时候做一个清理工作。

override dispose()记得调用super.dispose()并且把它放在方法体的最后

8: StatelessWidget和StatefulWidget之间的抉择
既然在Flutter里面有StatelessWidget和StatefulWidget这两种类型的组件,那在这二者之间如何抉择呢?

记住标准就是:
在这个组件的生命周期内,是否有会变化的数据,这个组件是否需要rebuild?

如果答案是yes,那你就需要一个StatefulWidget而不会StatelessWidget。

9:StatefulWidget的2个组成部分

组件的构造函数部分

class MyStatefulWidget extends StatefulWidget {
    MyStatefulWidget({
        Key key,
        this.color,
    }): super(key: key);
    
    final Color color;

    @override
    _MyStatefulWidgetState createState() => new _MyStatefulWidgetState();
}

这部分是一个StatefulWidget的public部分。这部分不会在一个组件的生命周期内发生改变,它只是接收一些参数以便它的State可以使用。比如上面这个例子里面的color这个参数。

Widget State定义部分

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
    ...
    @override
    Widget build(BuildContext context){
        ...
    }
}

_MyStatefulWidgetState是这个Widget在其生命周期内变化的部分,也是使得这个Widget能够rebuild的部分。
_MyStatefulWidgetState通过widget.{name of the variable}可以获取存在MyStatefulWidget内的任意变量。例如这里,可以通过widget.color获取color变量。

10:Widget的唯一标识-key
在Flutter里面,每一个组件都是唯一标识的,这个唯一标识是在build的时候被定义的。

这个唯一的标识就是组件的可选参数:Key. 加入key缺省了,系统会默认给你创建一个。

在某些情形下,你必须强制制定key,以便你可以通过这个key获取到这个组件。

你可以通过下面的一些helper来达到上面的目的:GlobalKey, LocalKey, UniqueKey或者ObjectKey。

GlobalKey保证这个key在整个application里面都是唯一的。

以下的例子就是保证myKey在整个application都是唯一的:

GlobalKey myKey = new GlobalKey();
    ...
    @override
    Widget build(BuildContext context){
        return new MyWidget(
            key: myKey
        );
    }

PS:由于此篇的篇幅已经够长,之前的思维导图里面的第二部分的篇幅也会很长,所以第二部分会放到下一篇文章讲解。

    原文作者:nanaistaken
    原文地址: https://segmentfault.com/a/1190000018863761
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞