开发者在进行Flutter开发时,大部分工作基本上少不了与StatelessWidget和StatefulWidget打交道。大家是否真的了解StatelessWidget和StatefulWidget?
讨论
我阅读了很多网上的文章,大部分会讲解两者的使用上的区别,一部分文章有解释这两者的区别。但是他们的解释有的是字面解释,有的是浅尝辄止,有的甚至是有一定的误导。
列出网上一些文章中的解释:
如果你对上述一些观点很认同的话,我觉得阅读本篇文章应该可以给你提供一个不一样的理解视角。
Widget
我们要比较StatelessWidget和StatefulWidget的区别,我们得先知道什么是Widget。
官方对Widget的解释是:
即Widget是部分界面的不可变的描述信息。
重要的事情说三遍:
我们从代码上看看Widget如何实现的不可变。Widget的代码如下:
@immutable
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key? key;
// 省略...
}
我们可以看到Widget左上角有一个@immutable
注解,这个注解的意思是所有的属性必须是final修饰,也就是Widget一旦初始化以后,其属性将不可变。
接下来我们再看看StatelessWidget和StatefulWidget的官方解释和相关代码:
abstract class StatelessWidget extends Widget {
const StatelessWidget({ Key? key }) : super(key: key);
@override
StatelessElement createElement() => StatelessElement(this);
@protected
Widget build(BuildContext context);
}
abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key? key }) : super(key: key);
@override
StatefulElement createElement() => StatefulElement(this);
@protected
@factory
State createState();
}
Widget总结
- StatelessWidget和StatefulWidget没有本质区别,他们的所有属性都是不可变的。它们都没法更新,除非用一个新的Widget去替换它们。
- StatefulWidget拥有一个可变的State。
这样我们就得到了一个结论:StatelessWidget和StatefulWidget的区别就在这个可变的State了。
新的问题又来了,这个State扮演了什么作用呢?
State
我们进行界面的修改,一般会调用state.setState()
方法。那这个方法是如何实现界面元素修改的呢?
void setState(VoidCallback fn) {
final dynamic result = fn() as dynamic;
_element!.markNeedsBuild();
}
setState方法很简单:
- 执行传入的函数;
-
_element
调用了markNeedsBuild
方法。
void markNeedsBuild() {
if (dirty)
return;
_dirty = true;
owner!.scheduleBuildFor(this);
}
-
_element
把自己的_dirty
属性设置为true; -
BuildOwner调用
scheduleBuildFor
方法。
void scheduleBuildFor(Element element) {
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
// 1
onBuildScheduled!();
}
_dirtyElements.add(element);
element._inDirtyList = true;
}
-
BuildOwner调用
onBuildScheduled
方法;
- 将element加入到dirtyElements中。
在合适的时候Flutter Engine会回调SchedulerBinding的handleDrawFrame
方法,最后会调用BuildOwner的buildScope
方法。
void buildScope(Element context, [ VoidCallback? callback ]) {
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
_dirtyElements[index].rebuild();
}
}
遍历dirtyElements元素,每个element调用rebuild
。
rebuild的作用是什么?没错,就是我们开头提到的对界面元素进行更新的操作。
结论
StatelessWidget和StatefulWidget的本质区别就是能否自我重新构建(self rebuild)。
一些思考
- 既然StatefulWidget的主要作用只是为了赋予了其自我重新构建(self rebuild)的能力,那为什么需要State呢?
- 既然StatefulWidget的功能更完善,为什么又提供一个StatelessWidget呢?
- 可不可以在开发中全部都使用StatefulWidget?
- 可不可以在开发中全部都使用StatelessWidget?
- 开发中如何选择StatelessWidget还是StatefulWidget?
实战分享
我们前面比较了StatelessWidget和StatefulWidget的区别,进行了一些分析,到底如何写出更好更优化的代码,现在我们就用Flutter官方的计数器Demo来练练手。
通过前面的分析,我们知道点击FloatingActionButton会调用_MyHomePageState的setState
进行rebuild。如下图所示:
细心的你可能发现问题了,我只是想修改Scaffold->Body->Center->Column->第二个Text中的文字。而Build的起点是Scaffold,这么长的构建链条相当于修改一个文字,把整个页面都重新构建了一次。就显然是一个无法忽视的问题。
修改的思路就是我们只需要在第二个Text上封装一个StatefulWidget,让这个StatefulWidget的setState去触发第二个Text的文字修改。
我们抽提一个CounterText:
class CounterText extends StatefulWidget {
final _CounterTextState state = _CounterTextState();
CounterText({
Key key,
}) : super(key: key);
@override
_CounterTextState createState() => state;
}
class _CounterTextState extends State<CounterText> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Container(
child: Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
);
}
}
CounterText的使用:
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
CounterText counterText = CounterText();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
counterText,
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: counterText.state._incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
这样就改造完成了。
总结:
我们需要对StatelessWidget和StatefulWidget有一个全面的了解,才能正确的使用他们。欢迎一起探讨和学习。