1. setState时,变量需要放在方法内部吗?
官网的计数器实例:
setState(() {
_counter++;
});
调用setState会调用build方法刷新,在调用setState之前同步设置变量的变化即可,不一定非要放在setState中。
下面这样也是可以的:
_counter++;
setState(() {});
在实际场景中,变化的代码可能很长,嵌套在一起不是很喜欢。
2. setState子widget如何刷新
实际开发中,可能会按功能拆分成多个widget。而多个widget是以树的形式存在,StatefulWidget和StatelessWidget会被频繁创建。StatelessWidget只要保证数据源刷新,其UI一定会刷新正确。
StatefulWidget中的State是包含声明周期的,也就是说StatefulWidget会被频繁创建,但其State初始化后被插入到配置树中,除切换tab,退出页面等,正常setState操作是不会销毁的。
class Filtrate extends StatefulWidget {
List<FiltrateConfig> configs;
@override
_FiltrateState createState() => _FiltrateState(this.configs);
}
class _FiltrateState extends State<Filtrate> {
List<FiltrateConfig> configs;
_FiltrateState(this.configs);
}
上述代码在_FiltrateState使用configs时,如果外部configs对象在初始化后,重新new一个新的对象,调用到Filtrate(configs),再setState,是不会传入到_FiltrateState的。因为_FiltrateState只会初始化一次,而configs的对象已经发生了变化,造成StatefulWidget与State中的configs不是一个对象,导致UI更新错误。
在State中,如果需要使用外部参数,可以直接调用widget.configs,不要定义两个configs。
3.状态管理
在build中有子widget,而想要更新子widget数据。
正常的iOS开发思路,会把子widget声明全局变量,然后暴露方法,在需要的时候刷新。而在flutter中,widget基本是局部变量,设置数据源,setState即可。
子widget自己管理数据源,并不是从外部传入,如何刷新。子widget改变数据,需要同步到外面怎么刷新。
InheritedWidget在flutter是一个很重要的概念:
- 定义数据模型,继承自InheritedWidget
class ShareDataWidget extends InheritedWidget {
ShareDataWidget({
@required this.data,
Widget child
}) :super(child: child);
final int data; //需要在子树中共享的数据,保存点击次数
//定义一个便捷方法,方便子树中的widget获取共享数据
static ShareDataWidget of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
}
//该回调决定当data发生变化时,是否通知子树中依赖data的Widget
@override
bool updateShouldNotify(ShareDataWidget old) {
//如果返回true,则子树中依赖(build函数中有调用)本widget
//的子widget的`state.didChangeDependencies`会被调用
return old.data != data;
}
}
- 在需要的地方调用ShareDataWidget.of()
@override
Widget build(BuildContext context) {
//使用InheritedWidget中的共享数据
return Text(ShareDataWidget
.of(context)
.data
.toString());
}
- 在父widget使用ShareDataWidget包裹
ShareDataWidget( //使用ShareDataWidget
data: count,
child: Container()
);
dependOnInheritedWidgetOfExactType:会更新子widget
getElementForInheritedWidgetOfExactType:不会更新,局部刷新原理使用此方法。
具体文章参考:
7.2 数据共享(InheritedWidget)
7.3 跨组件状态共享(Provider)
小结:
在flutter中InheritedWidget是一个非常重要的概念,理解之后再使用Provider能知道为什么框架会那么写。
4.局部刷新
@immutable
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
...
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
系统框架对是否需要更新Widget已经做了处理,在性能上是没什么问题。但开发能做的优化,就是只刷新目标UI。
Provider
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 通过 Provider 组件封装数据资源
return ChangeNotifierProvider.value(
value: CounterModel(),// 需要共享的数据资源
child: MaterialApp(
home: FirstPage(),
)
);
}
}
// 第一个页面,负责读数据
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 取出资源
final _counter = Provider.of<CounterModel>(context);
return Scaffold(
// 展示资源中的数据
body: Text('Counter: ${_counter.counter}'),
// 跳转到 SecondPage
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.of(context).push(MaterialPageRoute(builder: (context) => SecondPage()))
));
}
}
// 第二个页面,负责读写数据
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 取出资源
final _counter = Provider.of<CounterModel>(context);
return Scaffold(
// 展示资源中的数据
body: Text('Counter: ${_counter.counter}'),
// 用资源更新方法来设置按钮点击回调
floatingActionButton:FloatingActionButton(
onPressed: _counter.increment,
child: TestIcon,
));
}
}
// 用于打印 build 方法执行情况的自定义控件
class TestIcon extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("TestIcon build");
return TestIcon();// 返回 Icon 实例
}
}
在点击按钮时,TestIcon也会被刷新。
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
// 使用 Consumer 来封装 counter 的读取
body: Consumer<CounterModel>(
//builder 函数可以直接获取到 counter 参数
builder: (context, CounterModel counter, _) => Text('Value: ${counter.counter}')),
// 使用 Consumer 来封装 increment 的读取
floatingActionButton: Consumer<CounterModel>(
//builder 函数可以直接获取到 increment 参数
builder: (context, CounterModel counter, child) => FloatingActionButton(
onPressed: counter.increment,
child: child,
),
child: TestIcon(),
),
);
}
}
Consumer 中的 builder 实际上就是真正刷新 UI 的函数
参考文章:30丨为什么需要做状态管理,怎么做?
总结
- 我只是知识的搬运工,目前flutter已看书籍:《Flutter实战》、《Flutter核心技术与实战》。
- 目前工程已接入flutter,从0到1搭建了一整套环境。对flutter有了更深的理解。
- 转换一门新的语言,能使用只是入门水平。需要在实践的同时,不断结合理论,才能让你的代码写的更好,让项目更稳定。
网友评论