Flutter状态管理3-InheritedWidget

官网关于状态管理的一段介绍:
Managing state

官方视频介绍:Inherited Widgets Explained - Flutter Widgets 101 Ep. 3


在Flutter的Widget树中,如果树中的底层Widget想要获取到一些上层的状态数据,通过build方法里的Widget构造函数传入数据是可行的一种,但是当层级较多时,那一直从上向下传递非常麻烦。

Flutter内置了一种依赖注入的方式或者说是一种全局提供数据的方式:Inherited Widgets
在这里插入图片描述

这里的Inherit并不是指的类class的继承,而是指的Widget层次结构上的继承,

一个继承自InheritedWidget的例子:

class InheritedContext extends InheritedWidget {
  
  //数据
  final InheritedTestModel inheritedTestModel;

  //点击+号的方法
  final Function() increment;

  //点击-号的方法
  final Function() reduce;

  InheritedContext({
    Key key,
    @required this.inheritedTestModel,
    @required this.increment,
    @required this.reduce,
    @required Widget child,
  }) : super(key: key, child: child);

  static InheritedContext of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(InheritedContext);
  }

  //是否重建widget就取决于数据是否相同
  @override
  bool updateShouldNotify(InheritedContext oldWidget) {
    return inheritedTestModel != oldWidget.inheritedTestModel;
  }
}

在底层的Widget中获取上层某个Widget中的数据,可以使用InheritedContext.of(InheritedContext),这时候Flutter就会从当前的context开始,逐层往上,直到找到Widget树中包含和定义的InheritedWidget类型一致的Widget,然后获取数据。

我们通常使用的Theme.of(context).primaryColor 就是这样的一个例子。Theme也是一个InheritedWidget

⚠️注意:

  1. InheritedWidget是不可变的immutable,所以其中的数据或者对外提供的服务都会被标记为final。所以也不应该存在给该数据重新赋值的情况。
  2. But the fact that something is final only means it can’t be reassigned, It does not mean it cannot change internally.
    比如这里的InheritedWidget中的一个value换成一个service,这个service可以是一个数据库的包装类对象、一个Web API的代理类对象、或者是一个本地资源的提供者,这个service对象可以有自己的内部状态,可以调用这个service的方法或者订阅一个stream流,如下代码所示:
InheritedNose.of(context).service.blow();


StreamBuilder(
	stream:InheritedNose.of(context).service.sneezes,
	builder: /** **)

使用InheritedWidget的正确姿势

  1. 在Widget子树中,如果哪个Widget中调用了XXXXWidget.of(context).data方法来获取InheritedWidget中的data,那么当前的这个子Widget便和InheritedWidget产生了依赖关系,当InheritedWidget中的data发生变化的时候,会调用InheritedWidget的
    updateShouldNotify()方法,如果该方法返回了true,那么Flutter Framework就会调用产生订阅关系的子Widget的didChangeDependencies()方法;

  2. 按照上面的理解,当Widget子树中,没有产生订阅关系的Widget在InheritedWidget中大data发生变化时,是不应该被重新创建的才对

这里引用了使用InheritedWidget被忽略的要点一文中的例子代码:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Welcome to InheritedWidget',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.yellow,
        primaryColor: Colors.white,
      ),
//      home: MyHomePage(title: 'Flutter Demo Home Page'),
      home: MyWidget(),
    );
  }
}

class MyWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return MyState();
  }
}

class MyState extends State<MyWidget> {
  int tmpData = 0;

  @override
  Widget build(BuildContext context) {
    print('MyWidget build');
    return Scaffold(
      appBar: AppBar(title: Text('Welcome to InheritedWidget')),

//      home: MyHomePage(title: 'Flutter Demo Home Page'),
      body: MyInherited(
        data: tmpData,
        child: Container(
            child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            children: <Widget>[
              WidgetA(),
              WidgetB(),
              FlatButton(
                child: Text("Click me"),
                onPressed: () {
                  setState(() {
                    print('onPressed');
                    tmpData += 1;
                  });
                },
              ),
            ],
          ),
        )),
      ),
    );
  }
}

class MyInherited extends InheritedWidget {
  final int data;

  MyInherited({this.data, Widget child}) : super(child: child) {
    print('MyInherited construct');
  }

  @override
  bool updateShouldNotify(MyInherited oldWidget) {
    bool result = oldWidget.data != this.data;
    print('MyInherited updateShouldNotify result = $result');
    return result;
  }

  static MyInherited of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(MyInherited);
  }
}

class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('WidgetA build');
    int data = MyInherited.of(context).data;
    return Text('WidgetA data = $data');
  }
}

class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('WidgetB build');
    return Text('WidgetB');
  }
}

自定义一个Widget MyInherited继承自InheritedWidget,里面包含了WidgetA和WidgetB和一个按钮,当点击按钮的时候,MyInherited中的数据data变化,按照我们上面的理解,理论上这时候应该只调用WidgetA的build方法,但是输出结果显示,WidgetB的方法也被调用了:

I/flutter ( 6912): onPressed
V/AudioManager( 6912): playSoundEffect   effectType: 0
V/AudioManager( 6912): querySoundEffectsEnabled...
I/flutter ( 6912): MyWidget build
I/flutter ( 6912): MyInherited construct
I/flutter ( 6912): MyInherited updateShouldNotify result = true
I/flutter ( 6912): WidgetA build
I/flutter ( 6912): WidgetB build

更详细的介绍可以参考
《Flutter实战》
7.2 数据共享(InheritedWidget)

那么该如何解决上面的问题呢?

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值