Flutter 嵌套小部件 setState 无法按预期工作

Posted

技术标签:

【中文标题】Flutter 嵌套小部件 setState 无法按预期工作【英文标题】:Flutter nested widgets setState does not work as intended 【发布时间】:2021-11-17 13:35:43 【问题描述】:

我有以下结构:

主块。主状态小部件。包含两个小部件 BlockABlockB块A。包含一个文本和一个按钮。 块 B。包含另一个小部件 BlockBCard,它将被使用多次(在本例中为两次)。

什么按预期工作?当我点击 BlockA 中的按钮时,BlockA 中的文本字段的内容根据需要更换 BlockBCard

现在是我的问题:

块B中。为了使用setState,我将BlockB 更改为StatefulWidget。 单击 BlockBCard 中的按钮,根据需要更改两个 BlockBCard 中的文本字段。 但 BlockA 中文本字段的内容并没有改变。

如何实现以下功能: 点击其中一个BlockBCard中的按钮,BlockA中的文本字段和BlockBCard中的两个文本字段都改变了吗?

单击 BlockBCard 之一中的按钮,BlockA 中的文本字段发生变化,BlockBCard 中的文本字段发生变化,但第二个 BlockBCard 中的文本字段没有改变。

示例代码:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());
int testCounter = 0;

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      home: MainBlock(),
    );
  

// ------------------------------------ Stateless Widget <<<

class MainBlock extends StatefulWidget 
  @override
  _MainBlockState createState() => _MainBlockState();


class _MainBlockState extends State<MainBlock> 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: Container(
        margin: EdgeInsets.all(30.0),
        color: Color(0xFF122C39),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Expanded(
              child: BlockA(
                counter: testCounter,
                button: () 
                  setState(() 
                    testCounter++;
                  );
                ,
              ),
            ),
            Expanded(
              child: BlockB(),
            ),
          ],
        ),
      ),
    );
  


class BlockA extends StatelessWidget 
  final int counter;
  final Function button;

  BlockA(this.counter, this.button);

  @override
  Widget build(BuildContext context) 
    return Container(
      margin: EdgeInsets.all(10.0),
      color: Color(0xFF265672),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Center(
            child: Text(
              counter.toString(),
              style: TextStyle(
                color: Color(0xFFFFFFFF),
                fontSize: 22.0,
              ),
            ),
          ),
          Center(
            child: GestureDetector(
              onTap: button,
              child: Text(
                'Button',
                style: TextStyle(
                  color: Color(0xFFFFFFFF),
                  fontSize: 22.0,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  


class BlockB extends StatefulWidget 
  @override
  _BlockBState createState() => _BlockBState();


class _BlockBState extends State<BlockB> 
  @override
  Widget build(BuildContext context) 
    return Container(
      margin: EdgeInsets.all(10.0),
      color: Color(0xFF265672),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Expanded(
            child: BlockBCard(
              counter: testCounter,
              button: () 
                setState(() 
                  testCounter++;
                );
              ,
            ),
          ),
          Expanded(
            child: BlockBCard(
              counter: testCounter,
            ),
          ),
        ],
      ),
    );
  


class BlockBCard extends StatelessWidget 
  final int counter;
  final Function button;

  BlockBCard(this.counter, this.button);

  @override
  Widget build(BuildContext context) 
    return Container(
      margin: EdgeInsets.all(30.0),
      color: Color(0xFF4C93C7),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Center(
            child: Text(
              counter.toString(),
              style: TextStyle(
                color: Color(0xFFFFFFFF),
                fontSize: 22.0,
              ),
            ),
          ),
          Center(
            child: GestureDetector(
              onTap: button,
              child: Text(
                'Button',
                style: TextStyle(
                  color: Color(0xFFFFFFFF),
                  fontSize: 22.0,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  

好的,我现在已经改进了我的代码,以便 BlockBCard's 中的按钮调用一个函数 myBrain 来执行计算,其结果在 中输出>块A

现在我想将 BlockBCard 的 中的文本字段增加 1,但仅在按下按钮的相应卡片中,而不是在所有卡片中。使用我当前的代码,所有卡片都错误地增加了 1。

在这个例子中只有两张卡,但在实现中会有多张卡。

这是我当前的代码。为了简化示例,我删除了 BlockB 并将 BlockBCard's 直接放入 MainBlock:

如何将 BlockBCard 中的文本字段增加 1,但仅在按下按钮的相应卡片中而不是在所有卡片中?

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      home: MainBlock(),
    );
  


// OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO MainBlock >>>
class MainBlock extends StatefulWidget 
  @override
  _MainBlockState createState() => _MainBlockState();


class _MainBlockState extends State<MainBlock> 
  int totalCounter = 0;
  int singleCounter = 0;

  void myBrain() 
    totalCounter = totalCounter + 5;
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: Container(
        margin: EdgeInsets.all(30.0),
        color: Color(0xFF122C39),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Expanded(
              child: BlockA(
                counter: totalCounter,
                button: () 
                  setState(() 
                    totalCounter = 0;
                  );
                ,
              ),
            ),
            Expanded(
              child: BlockBCard(
                counter: singleCounter,
                button: () 
                  setState(() 
                    myBrain();
                    singleCounter++;
                  );
                ,
              ),
            ),
            Expanded(
              child: BlockBCard(
                counter: singleCounter,
                button: () 
                  setState(() 
                    myBrain();
                    singleCounter++;
                  );
                ,
              ),
            ),
          ],
        ),
      ),
    );
  

// OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO MainBlock <<<

// OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO BlockA >>>
class BlockA extends StatelessWidget 
  final int counter;
  final Function button;

  BlockA(this.counter, this.button);

  @override
  Widget build(BuildContext context) 
    return Container(
      margin: EdgeInsets.all(10.0),
      color: Color(0xFF265672),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Center(
            child: Text(
              counter.toString(),
              style: TextStyle(
                color: Color(0xFFFFFFFF),
                fontSize: 22.0,
              ),
            ),
          ),
          Center(
            child: GestureDetector(
              onTap: button,
              child: Text(
                'Button',
                style: TextStyle(
                  color: Color(0xFFFFFFFF),
                  fontSize: 22.0,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  


// OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO BlockA <<<
// OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO BlockBCard >>>

class BlockBCard extends StatelessWidget 
  final int counter;
  final Function button;

  BlockBCard(this.counter, this.button);

  @override
  Widget build(BuildContext context) 
    return Container(
      margin: EdgeInsets.all(30.0),
      color: Color(0xFF4C93C7),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Center(
            child: Text(
              counter.toString(),
              style: TextStyle(
                color: Color(0xFFFFFFFF),
                fontSize: 22.0,
              ),
            ),
          ),
          Center(
            child: GestureDetector(
              onTap: button,
              child: Text(
                'Button',
                style: TextStyle(
                  color: Color(0xFFFFFFFF),
                  fontSize: 22.0,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  

// OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO BlockBCard <<<

【问题讨论】:

【参考方案1】:

您遇到了问题,因为当您更改 blockB 的值时,它无法重建 ta blockA,但如果您想从 blockB 更改 blockA,它会更改全局值,您应该像 blockA 一样传递。


import 'package:flutter/material.dart';

void main() => runApp(MyApp());
int testCounter = 0;

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      home: MainBlock(),
    );
  

// ------------------------------------ Stateless Widget <<<

class MainBlock extends StatefulWidget 
  @override
  _MainBlockState createState() => _MainBlockState();


class _MainBlockState extends State<MainBlock> 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: Container(
        margin: EdgeInsets.all(30.0),
        color: Color(0xFF122C39),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Expanded(
              child: BlockA(
                counter: testCounter,
                button: () 
                  setState(() 
                    testCounter++;
                  );
                ,
              ),
            ),
            Expanded(
              child: BlockB(
                  counter: testCounter,
                  button: () 
                    setState(() 
                      testCounter++;
                    );
                  ),
            ),
          ],
        ),
      ),
    );
  


class BlockA extends StatelessWidget 
  final int counter;
  final Function button;

  BlockA(this.counter, this.button);

  @override
  Widget build(BuildContext context) 
    return Container(
      margin: EdgeInsets.all(10.0),
      color: Color(0xFF265672),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Center(
            child: Text(
              counter.toString(),
              style: TextStyle(
                color: Color(0xFFFFFFFF),
                fontSize: 22.0,
              ),
            ),
          ),
          Center(
            child: GestureDetector(
              onTap: button,
              child: Text(
                'Button',
                style: TextStyle(
                  color: Color(0xFFFFFFFF),
                  fontSize: 22.0,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  


class BlockB extends StatefulWidget 
  final int counter;
  final Function button;

  BlockB(this.counter, this.button);

  @override
  _BlockBState createState() => _BlockBState();


class _BlockBState extends State<BlockB> 
  @override
  Widget build(BuildContext context) 
    return Container(
      margin: EdgeInsets.all(10.0),
      color: Color(0xFF265672),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Expanded(
            child: BlockBCard(
              counter: testCounter,
              button:widget.button,
            ),
          ),
          Expanded(
            child: BlockBCard(
              counter: testCounter,
            ),
          ),
        ],
      ),
    );
  


class BlockBCard extends StatelessWidget 
  final int counter;
  final Function button;

  BlockBCard(this.counter, this.button);

  @override
  Widget build(BuildContext context) 
    return Container(
      margin: EdgeInsets.all(30.0),
      color: Color(0xFF4C93C7),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Center(
            child: Text(
              counter.toString(),
              style: TextStyle(
                color: Color(0xFFFFFFFF),
                fontSize: 22.0,
              ),
            ),
          ),
          Center(
            child: GestureDetector(
              onTap: button,
              child: Text(
                'Button',
                style: TextStyle(
                  color: Color(0xFFFFFFFF),
                  fontSize: 22.0,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  


【讨论】:

啊哈你好再次;)我们大多做同样的事情但是我认为使用可变全局变量确实是这里犯的错误。当然你需要把它传递下去,但是如果你明白你永远不应该创建一个可变的全局变量,那么无论如何你都不得不把它传递下去 @Lulupointu yap 明白可变全局变量确实是个错误。【参考方案2】:

正如@Jahidul Islam 所解释的,您必须将数据从MainBlock 传递到BlocB,就像从MainBlock 传递到BlocA 一样。

通常,在 Flutter 中,正是出于这个原因,您希望避免使用可变全局变量。当一个变量更新时,flutter知道它。如果您致电setState,小部件树波纹管 将被重建。 这就是为什么您经常会听到“提升状态”的原因,因为状态必须包含在足够高的小部件中,以便它可以通过小部件树传递给每个需要它的小部件。在您的情况下,由于BlocA BlockBCard 需要counter,因此必须在较低的共同祖先中创建它:MainBlock

这是具体的实现,但最重要的是理解上面解释的推理:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      home: MainBlock(),
    );
  


class MainBlock extends StatefulWidget 
  @override
  _MainBlockState createState() => _MainBlockState();


class _MainBlockState extends State<MainBlock> 
  int testCounter = 0;

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: Container(
        margin: EdgeInsets.all(30.0),
        color: Color(0xFF122C39),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Expanded(
              child: BlockA(
                counter: testCounter,
                button: () 
                  setState(() 
                    testCounter++;
                  );
                ,
              ),
            ),
            Expanded(
              child: BlockB(
                counter: testCounter,
                button: () 
                  setState(() 
                    testCounter++;
                  );
                ,
              ),
            ),
          ],
        ),
      ),
    );
  


class BlockA extends StatelessWidget 
  final int counter;
  final VoidCallback button;

  BlockA(required this.counter, required this.button);

  @override
  Widget build(BuildContext context) 
    return Container(
      margin: EdgeInsets.all(10.0),
      color: Color(0xFF265672),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Center(
            child: Text(
              counter.toString(),
              style: TextStyle(
                color: Color(0xFFFFFFFF),
                fontSize: 22.0,
              ),
            ),
          ),
          Center(
            child: GestureDetector(
              onTap: button,
              child: Text(
                'Button',
                style: TextStyle(
                  color: Color(0xFFFFFFFF),
                  fontSize: 22.0,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  


class BlockB extends StatelessWidget 
  final int counter;
  final VoidCallback button;

  BlockB(required this.counter, required this.button);

  @override
  Widget build(BuildContext context) 
    return Container(
      margin: EdgeInsets.all(10.0),
      color: Color(0xFF265672),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Expanded(
            child: BlockBCard(
              counter: counter,
              button: button,
            ),
          ),
          Expanded(
            child: BlockBCard(
              counter: counter,
            ),
          ),
        ],
      ),
    );
  


class BlockBCard extends StatelessWidget 
  final int counter;
  final VoidCallback? button;

  BlockBCard(required this.counter, this.button);

  @override
  Widget build(BuildContext context) 
    return Container(
      margin: EdgeInsets.all(30.0),
      color: Color(0xFF4C93C7),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Center(
            child: Text(
              counter.toString(),
              style: TextStyle(
                color: Color(0xFFFFFFFF),
                fontSize: 22.0,
              ),
            ),
          ),
          Center(
            child: GestureDetector(
              onTap: button ?? (() ),
              child: Text(
                'Button',
                style: TextStyle(
                  color: Color(0xFFFFFFFF),
                  fontSize: 22.0,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  

【讨论】:

我通过小部件树传递状态状态来查看这里的逻辑。在我的编码水平上,为什么要避免使用可变全局变量尚不清楚。 你亲眼看到全局变量产生了什么样的不良行为,你没有吗? 是的,现在更清楚了,我改进了我的代码并理解了这种关系。但目前尚不清楚如何仅在按下按钮的相应卡片中而不是在所有其他卡片中将文本字段增加 1。【参考方案3】:

如果我们刷新MainBlock,它的孩子也会得到更新的状态。我们可以使用回调。

开启BlocKB


class BlockB extends StatefulWidget 
  final VoidCallback ontap;
  const BlockB(
    Key? key,
    required this.ontap,
  ) : super(key: key);
  @override
  _BlockBState createState() => _BlockBState();


class _BlockBState extends State<BlockB> 
  @override
  Widget build(BuildContext context) 
    return Container(
      margin: EdgeInsets.all(10.0),
      color: Color(0xFF265672),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Expanded(
            child: BlockBCard(
              counter: testCounter,
              button: () 
                // setState(()  /// we dont need it while parent widget will refress it
                testCounter++;
                // );

                widget.ontap();
              ,
            ),
          ),
          Expanded(
            child: BlockBCard(
              counter: testCounter,
              button: () 
                /// you may also wanted to increment here

                testCounter++;

                widget.ontap();
              ,
            ),
          ),
        ],
      ),
    );
  


在主块上


       Expanded(
              child: BlockB(
                ontap: () 
                  setState(() );
                ,
              ),
            ),

【讨论】:

以上是关于Flutter 嵌套小部件 setState 无法按预期工作的主要内容,如果未能解决你的问题,请参考以下文章

编辑文本控制器更新小部件值而不调用 setState() - Flutter

Flutter - setState 不更新内部有状态小部件

如何在 Flutter 的 StatefulWidget 类中的 setState() 方法上停止小部件重新加载

Flutter:在构造函数中调用 setState():_SharesListState#6c96a(生命周期状态:已创建,无小部件,未安装)

setState() 清除表单元素数据flutter中的数据

Flutter - 从 websocket 消息动态创建小部件 - 未处理的异常:在构造函数中调用 setState()