共计 6176 个字符,预计需要花费 16 分钟才能阅读完成。
读前须知:此篇文章基本上是 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 的概念
接下来文章会按照以下结构去展开:
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 的话就如下图所示:
3:什么是 Context 一个 context 标识了一个 widget 在 widget tree 的结构中是在哪个地方被 build 的。简而言之就是一个 context 标明了一个 widget 是挂载在 widget tree 的那个节点的。
一个 context 只属于一个 widget。假如一个 widget A 有子组件,那么 widget A 的 context 会变成子组件的父 context。
以上文提到的 widget tree 为例子,假如我们画出它的 context,如下图所示(一个颜色代表一个 context):
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 产出联系而因此变为可用的。
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:由于此篇的篇幅已经够长,之前的思维导图里面的第二部分的篇幅也会很长,所以第二部分会放到下一篇文章讲解。