Flutter中标准的数据传递方法是通过属性传递,类似于react中的props。对于复杂点的UI,这样有时候会比较繁琐。一个属性可能需要跨越很多层传递给子子组件。
对于跨层传递,flutter原生支持如下两种方案:InheritedWidget和InheritedModel。
InheritedWidget
有些Flutter使用经验的开发同学,可能比较熟悉这样的代码。
通过Theme访问当前的界面风格:Theme.of(context).primaryColor
通过MediaQuery访问屏幕大小:MediaQuery.of(context).size
这两个类都是通过InheritedWidget实现的典范。调用组件可以通过Theme.of方法找到上层最近的Theme父组件,跨层获取到其属性,同时会建立调用组件和上层组件的观察者关系。当上层组件属性修改的时候,触发调用组件的更新。
我们通过下面简单的计数器例子,演示下InheritedWidget如何用。如下两个类:InheritedContainer包含了状态计数num属性,Counter通过inheritFromWidgetOfExactType方法找到上层的InheritedContainer组件,并获取计数num并展示在一个按钮里面。
class InheritedContainer extends InheritedWidget {
static InheritedContainer of(BuildContext context) {
return context.inheritFromWidgetOfExactType(InheritedContainer) as InheritedContainer;
}
int num = 0;
InheritedContainer({
Key key,
@required Widget child,
}): super(key: key, child: child);
@override
bool updateShouldNotify(InheritedContainer oldWidget) {
return true;
}
}
class Counter extends StatelessWidget {
@override
Widget build(BuildContext context) {
InheritedContainer state = InheritedContainer.of(context);
return RaisedButton(
child: Text('${state.num}'),
onPressed: () {},
);
}
}
这样把Counter放到InheritedContainer下层任何位置,Counter都能获取到InheritedContainer的计数属性num,不用手动传递属性。
InheritedContainer(
child: Counter(),
)
细心的小伙伴发现我们的计数器现在是无法修改状态的。支持修改的话,我们就需要InheritedWidget和StatefulWidget联用了。我们可以把InheritedContainer放在一个StatefulWidget下面,并把状态移动到StatefulWidget的状态上。
class DataContainer extends StatefulWidget {
@override
DataContainerState createState() => DataContainerState();
}
class DataContainerState extends State<DataContainer> {
int num = 0;
void incNum() {
setState(() {
this.num++;
});
}
@override
Widget build(BuildContext context) {
return InheritedContainer(
num: num,
parent: this,
child: Counter(),
);
}
}
class InheritedContainer extends InheritedWidget {
final int num;
final DataContainerState parent;
static InheritedContainer of(BuildContext context) {
return context.inheritFromWidgetOfExactType(InheritedContainer) as InheritedContainer;
}
InheritedContainer({
Key key,
@required Widget child,
@required this.parent,
@required this.num,
}): super(key: key, child: child);
@override
bool updateShouldNotify(InheritedContainer oldWidget) {
return num != oldWidget.num;
}
}
class Counter extends StatelessWidget {
@override
Widget build(BuildContext context) {
InheritedContainer c = InheritedContainer.of(context);
return RaisedButton(
child: Text('${c.num}'),
onPressed: c.parent.incNum,
);
}
}
注意updateShouldNotify方法,DateContainerState重新构建一个InheritedContainer的时候,就会调用这个方法。该方法返回bool,表示是否需要下层的观察者组件更新。
InheritedModel
InheritedModel可以理解为InheritedWidget的升级版。
前面提到InheritedWidget更新的时候,会调用updateShouldNotify方法询问是否需要通知观察者更新。这个更新粒度很粗,所有观察者都会更新。设想如果InheritedWidget有多个属性a, b, c,下层有A观察a属性,B组件观察b属性,C组件观察c属性。当只有a属性修改的时候,也会同时触发A、B、C三个组件的更新。其中两个更新的浪费的。
InheritedModel使用方法略微不同。获取上层组件属性的时候,需要通过InheritedModel.inheritFrom<Inherited>(context, aspect)方法,并提供一个aspect,表示观察上层组件的什么属性。这样当InheritedModel的属性修改的时候,InheritedModel会对观察者们一一调用updateShouldNotifyDependent方法,确定观察者是否需要更新。
如下例子两个组件Counter1和Counter2分别观察了num1, num2属性。修改其中一个状态的时候不会引起两个组件同时更新。
class DataContainer extends StatefulWidget {
@override
DataContainerState createState() => DataContainerState();
}
class DataContainerState extends State<DataContainer> {
int num1 = 0;
void incNum1() {
setState(() {
this.num1++;
});
}
int num2 = 0;
void incNum2() {
setState(() {
this.num2++;
});
}
@override
Widget build(BuildContext context) {
return Inherited(
num1: num1,
num2: num2,
parent: this,
child: const Child()
);
}
}
class Child extends StatelessWidget {
const Child();
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Counter1(),
Counter2(),
],
);
}
}
class Inherited extends InheritedModel<String> {
final int num1;
final int num2;
final DataContainerState parent;
static Inherited of(BuildContext context, String aspect) {
return InheritedModel.inheritFrom<Inherited>(context, aspect: aspect) as Inherited;
}
Inherited({
Key key,
@required Widget child,
this.num1,
this.num2,
this.parent,
}): super(key: key, child: child);
@override
bool updateShouldNotify(Inherited oldWidget) {
return num1 != oldWidget.num1 || num2 != oldWidget.num2;
}
@override
bool updateShouldNotifyDependent(Inherited oldWidget,
Set<String> dependencies) {
var updateA = num1 != oldWidget.num1 && dependencies.contains('num1');
var updateB = num2 != oldWidget.num2 && dependencies.contains('num2');
return updateA || updateB;
}
}
class Counter1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
Inherited c = Inherited.of(context, 'num1');
return RaisedButton(
child: Text('${c.num1}'),
onPressed: c.parent.incNum1,
);
}
}
class Counter2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
Inherited c = Inherited.of(context, 'num2');
return RaisedButton(
child: Text('${c.num2}'),
onPressed: c.parent.incNum2,
);
}
}
const Child()是什么鬼?
const组件使flutter在更新的时候可以重用cache,提高性能。如果本次构建的组件和上次的属性相同又是const组件,则当前分支的更新停止。如果没有中间的const Child,Counter1, Counter2仍然会同时更新的。因为没有const时候整个分支都会重新构建。