flutter Sate 通过widget.xxx 获取StatefulWidget参数值之坑

Sate获取StatefulWidget参数值常见的两种方式

1、StatefulWidget传参给State,在Sate直接使用StatefulWidget传来的参数值,如下:

class TestStatefulWidget extends StatefulWidget {
  //参数 
  final String _xxx;

  TestStatefulWidget(this._xxx);

  @override
  _TestStatefulWidgetState createState() => _TestStatefulWidgetState(this._xxx);
}

class _TestStatefulWidgetState extends State<TestStatefulWidget> {
  final _xxx;

  _TestStatefulWidgetState(this._xxx);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(_xxx), //直接使用StatefulWidget传来的参数值
    );
  }
}

2、StatefulWidget不传参给State,在Sate通过widget.xxx获取StatefulWidget的参数值,如下:

class TestStatefulWidget extends StatefulWidget {
  //参数
  final String _xxx;

  TestStatefulWidget(this._xxx);

  @override
  _TestStatefulWidgetState createState() => _TestStatefulWidgetState();
}

class _TestStatefulWidgetState extends State<TestStatefulWidget> {
  _TestStatefulWidgetState();

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(widget._xxx), //通过widget获取StatefulWidget参数值
    );
  }
}

Sate通过widget.xxx获取StatefulWidget参数值之坑

上面两种获取参值方式的差别在于StatefulWidget有没有将参数直接传给State,Sate是否通过widget.xxx获取StatefulWidget参数值;
下面通过实例来演示这两种获取参数值的差别,如下:

class StatefulWidgetParamPage extends StatefulWidget {
  final String title;

  StatefulWidgetParamPage({Key key, this.title}) : super(key: key);

  @override
  _StatefulWidgetParamPageState createState() =>
      _StatefulWidgetParamPageState();
}

class _StatefulWidgetParamPageState extends State<StatefulWidgetParamPage> {
  int i;

  @override
  void initState() {
    super.initState();
    i = 0;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Container(
          alignment: Alignment.center,
          width: double.infinity,
          height: double.infinity,
          child: Column(
            children: <Widget>[
              TestWidgetA(
                  TestModel(
                    'WidgetA参数:',
                  ),
                  i.toString()),
              TestWidgetB(TestModel('WidgetB参数:'), i.toString()),
              RaisedButton(
                  onPressed: () {
                    i++;
                    setState(() {});
                  },
                  child: Text('值增加刷新')),
              RaisedButton(
                  onPressed: () {
                    i++;
                  },
                  child: Text('值增加不刷新')),
              RaisedButton(
                  onPressed: () {
                    setState(() {});
                  },
                  child: Text('只刷新')),
            ],
          )),
    );
  }
}

class TestModel {
  String title;
  dynamic value;

  TestModel(
    this.title,
  ) {
    var r = new Random();
    value = r.nextInt(1000); //用于区分对象是否有重新创建,对象重新创建,值会变化
  }

  @override
  String toString() {
    return '$title  自定义对象${value ?? ''}';
  }
}

class TestWidgetA extends StatefulWidget {
  final TestModel model;
  final String value;

  TestWidgetA(this.model, this.value);

  @override
  _TestWidgetAState createState() => _TestWidgetAState();
}

class _TestWidgetAState extends State<TestWidgetA> {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: Container(
        alignment: Alignment.center,
        child: Text(
            '${widget?.model?.toString()}    change value=${widget?.value}\ncontext=$context\n'), //展示传参数据 值随刷新变
      ),
      onTap: () {
        print(
            '${widget?.model?.toString()}    change value=${widget?.value}\ncontext=$context\n'); //值不变,刷新后widget重新创建,值才变
        /*setState(() {//内部刷新

        });*/
      },
    );
  }
}

class TestWidgetB extends StatefulWidget {
  final TestModel model;
  final String value;

  TestWidgetB(this.model, this.value);

  @override
  _TestWidgetBState createState() => _TestWidgetBState(this.model, this.value);
}

class _TestWidgetBState extends State<TestWidgetB> {
  final TestModel model;
  final String value;

  _TestWidgetBState(this.model, this.value);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: Container(
        alignment: Alignment.center,
        child: Text(
            '${model?.toString()}    change value=$value\ncontext=$context\n'), //展示传参数据  值不随刷新变
      ),
      onTap: () {
        print(
            '${model?.toString()}    change value=$value\ncontext=$context\n'); //值不随刷新变
        /*setState(() {//内部刷新

        });*/
      },
    );
  }
}

上面代码比较多,可以先不看,先看下面的实例效果或自行跑demo,不懂再来看代码和代码说明
代码说明:

  • 上面代码通过TestWidgetA、TestWidgetB两个StatefulWidget来展示Sate获取StatefulWidget参数值不同方式带来的不同效果;
  • TestWidgetA:StatefulWidget不传参给State,在Sate通过widget.xxx获取StatefulWidget的参数值;
  • TestWidgetB:StatefulWidget传参给State,在Sate直接使用StatefulWidget传来的参数值;
  • TestWidgetA、TestWidgetB参数值有:自定义对象TestModel,外部计数值;
  • TestWidgetA、TestWidgetB除State获取StatefulWidget参数值方式不一样,其它都一样,同时展示StatefulWidget参数值:自定义对象、外部计数参数值;
  • 自定义对象TestModel:
    1、自定义对象TestModel,在构造方法初始化里用随机数赋值,可通过观察随机数值有没有变化来判断自定义对象有没有重新创建
    2、自定义对象TestModel作为TestWidgetA、TestWidgetB两个StatefulWidget的参数,并且自定义对象TestModel是直接实例化传给widgetTestWidgetA、TestWidgetB作为参数的;
  • 外部计数值,用于观察外部值变化 ,Sate获取StatefulWidget参数值是否会跟着变化;
  • TestWidgetA、TestWidgetB展示各自的context是为了说明各自的State没重新创建,上面例子不会引起TestWidgetA和TestWidgetB各自Sate的重新创建;
  • 三个按键功能分别是: 外部计数值增加并刷新界面、 外部计数值增加不刷新界面、只刷新界面;
  • 还有TestWidgetA、TestWidgetB本身的点击事件,点击在log打印,可实时看到传参值,分为内部刷新和内部不刷新两种;

外部计数值增加增加并刷新界面效果:

外部计数值增加并刷新界面效果.gif

效果说明:

  • 界面刷新时,每刷新一次,TestWidgetA(Sate 通过widget.xxx获取StatefulWidget参数值)的自定义对象参数值发生了重新创建,同时外面传来的参数值随着外面值的变化而变化;
  • 界面刷新时,TestWidgetB(Sate 没有通过widget.xxx获取StatefulWidget参数值)的自定义对象参数值没有发生重新创建,外面传来的参数值也没有随着外面值的变化而变化,也就是TestWidgetB参数值没有发生变化;

外部计数值增加不刷新界面效果:

外部计数值增加不刷新界面效果.gif

效果说明:

  • 只是值增加时,TestWidgetA、TestWidgetB的自定义对象参数没有发生重新创建,外面传来的参数值也没有随着外面值的变化而变化;
  • 再刷新时,TestWidgetA(Sate 通过widget.xxx获取StatefulWidget参数值)的自定义对象参数值马上发生了重新创建,同时外面传来的参数值随着外面值的变化而变化;TestWidgetB(Sate 没有通过widget.xxx获取StatefulWidget参数值)参数值没有发生变化;

只刷新界面效果:

只刷新界面效果.gif

效果说明:

  • 只刷新界面时,TestWidgetA(Sate 通过widget.xxx获取StatefulWidget参数值)的自定义对象参数值发生了重新创建;
  • TestWidgetB(Sate 没有通过widget.xxx获取StatefulWidget参数值)参数值不会发生变化;

不刷新,连续点击获取实时参数值效果:

连续点击获取实时参数值效果.png

效果说明:

  • 不刷新,实时获取TestWidgetA、TestWidgetB参数值,TestWidgetA、TestWidgetB参数值都不会发生变化;

内部刷新,连续点击获取实时参数值效果:

内部刷新,连续点击获取实时参数值效果.png

效果说明:

  • 内部刷新,实时获取TestWidgetA、TestWidgetB参数,TestWidgetA、TestWidgetB参数都不会发生变化;

总结

StatefulWidget传参给State,在Sate直接使用StatefulWidget传来的参数值:

  • 无论StatefulWidget内部刷新界面、外部刷新界面,外部参数值变化,在Sate直接获取StatefulWidget的参数值都不会改变;

StatefulWidget不传参给State,在Sate通过widget.xxx获取StatefulWidget传来的参数值:

  • StatefulWidget内部刷新界面,外面值变化时,在Sate通过widget.xxx获取参数值不会变化;
  • 外部界面刷新,在Sate通过widget.xxx获取StatefulWidget的参数值随外面值变化而变化;如果自定义对象直接实例化作为StatefulWidget参数,自定义对象参数值会随刷新而发生重新创建,这点需要重点留意,有可能界面刷新,自定义对象定义里的值就变成初始化的值了

原因分析

1、外部界面刷新:

  • 外部界面刷新时,Sate里的widget发生了重新创建
    对于StatefulWidget,Sate和Widget是分离的,widget是一个临时配置对象,外部界面刷新时,Sate里的widget会发生重新创建初始化,widget重新创建初始化,必会重新执行StatefulWidget的构造方法,StatefulWidget的构造方法重新构建,StatefulWidget的构造方法里的参数必会跟着重新初始化传入,所以在Sate通过widget.xxx获取StatefulWidget的参数值随外部刷新而变化;
  • 外部界面刷新时,Sate没有发生重新创建
    由于外部界面刷新时,Sate没有发生重新创建,StatefulWidget将参数传给State,也就是拷贝了一份参数值给Sate,所以即使widget发生了重新创建,Sate获取的到StatefulWidget的参数值还是最开始StatefulWidget拷贝的参数值,没有变化;

2、StatefulWidget内部刷新:

  • StatefulWidget内部刷新时,Sate和widget都没有发生重新创建,所以无论Sate是否通过widget.xxx获取StatefulWidget传来的参数值,都没有变化;

温馨提示:源码里大多都是通过widget.xxx来获取StatefulWidget参数值,所以并不是说widget.xxx不可取,这两种方式各有优劣势,用哪一种根据自己的业务进行取舍,文章主要是为了说明Sate 通过widget.xxx 获取StatefulWidget参数值需要注意参数值可能随外部环境变化而会变化。

demo传送门

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容