在Flutter中向有状态的小部件传递数据

280

我想知道在创建有状态的小部件时传递数据的推荐方式是什么。

我见过的两种风格是:

class ServerInfo extends StatefulWidget {

  Server _server;

  ServerInfo(Server server) {
    this._server = server;
  }

  @override
    State<StatefulWidget> createState() => new _ServerInfoState(_server);
}

class _ServerInfoState extends State<ServerInfo> {
  Server _server;

  _ServerInfoState(Server server) {
    this._server = server;
  }
}

这种方法会将值保存在 ServerInfo _ServerInfoState 中,看起来有点浪费。

另一种方法是使用 widget._server

class ServerInfo extends StatefulWidget {

  Server _server;

  ServerInfo(Server server) {
    this._server = server;
  }

  @override
    State<StatefulWidget> createState() => new _ServerInfoState();
}

class _ServerInfoState extends State<ServerInfo> {
  @override
    Widget build(BuildContext context) {
      widget._server = "10"; // Do something we the server value
      return null;
    }
}

这似乎有些反常,因为状态不再存储在_ServerInfoSate中,而是存储在小部件中。

这种情况下是否有最佳实践?


6
构造函数可以简化为ServerInfo(this._server); - Günter Zöchbauer
这个问题之前已经被问过了:https://dev59.com/56vka4cB1Zd3GeqPuYMk - Blasanka
1
这个回答解决了你的问题吗?在Flutter中将数据传递给StatefulWidget并在其状态中访问它 - moonvader
这个答案是在一个月前添加的:https://dev59.com/56vka4cB1Zd3GeqPuYMk。 - Blasanka
这个回答解决了你的问题吗?不使用构造函数将StatefulWidget数据传递给State类 - Blasanka
8个回答

519

不要通过其构造函数将参数传递给State。您应该仅使用this.widget.myField访问参数。

不仅编辑构造函数需要大量手动工作; 它并没有带来任何好处。没有理由复制Widget的所有字段。

编辑:

以下是一个示例:

class ServerIpText extends StatefulWidget {
  final String serverIP;

  const ServerIpText ({ Key? key, this.serverIP }): super(key: key);

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

class _ServerIpTextState extends State<ServerIpText> {
  @override
  Widget build(BuildContext context) {
    return Text(widget.serverIP);
  }
}

class AnotherClass extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: ServerIpText(serverIP: "127.0.0.1")
    );
  }
}

54
进一步评论:无论你通过构造函数传递什么给 State 对象,它都不会被更新! - Jonah Williams
12
我在这里,不理解评论的意思。“不要使用构造函数向State传递参数”。那么我该如何向State传递参数呢? - KhoPhi
14
@Rexford通过使用widget字段,已经可以访问到Stateful的所有属性。 - Rémi Rousselet
11
如果我想使用"foo"来预填充一个文本框,并且仍然允许用户编辑它,那么我应该在状态中再添加另一个"foo"属性吗? - Said Saifi
5
另一方面,将所有的对象都放在小部件中而不是状态中似乎很奇怪。 - Andrey Gordeev
显示剩余10条评论

67

最佳方法是不要通过其构造函数向State类传递参数。您可以轻松地在State类中使用widget.myField进行访问。

例如

class UserData extends StatefulWidget {
  final String clientName;
  final int clientID;
  const UserData(this.clientName,this.clientID);

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

class UserDataState extends State<UserData> {
  @override
  Widget build(BuildContext context) {
    // Here you direct access using widget
    return Text(widget.clientName); 
  }
}

在导航屏幕时传递数据:

 Navigator.of(context).push(MaterialPageRoute(builder: (context) => UserData("WonderClientName",132)));

20

在 @RémiRousselet 的回答基础上,回答 @user6638204 的问题:如果你想传递初始值,并且仍然能够稍后在状态中更新它们:

class MyStateful extends StatefulWidget {
  final String foo;

  const MyStateful({Key key, this.foo}): super(key: key);

  @override
  _MyStatefulState createState() => _MyStatefulState(foo: this.foo);
}

class _MyStatefulState extends State<MyStateful> {
  String foo;

  _MyStatefulState({this.foo});

  @override
  Widget build(BuildContext context) {
    return Text(foo);
  }
}

12
我们可以直接使用 initState 来做一些像 foo = widget.foo 这样的事情,不需要传递给构造函数。 - Aqib
1
@SteevJames 组件 MyStateful 有一个可选的命名参数(属性)。您可以通过调用 MyStateful(foo: "my string",) 来创建此组件。 - Kirill Karmazin
@Aqib,initState在以下情况下无法解决问题:例如,您使用空参数创建了Statefull小部件,并且正在等待数据加载。当数据加载完成后,您希望使用最新数据更新Statefull小部件,在这种情况下,当您调用MyStatefull(newData)时,它的initState()不会被调用!在这种情况下,将调用didUpdateWidget(MyStatefull oldWidget),您需要将参数oldWidget.getData()中的数据与widget.data进行比较,如果不同,则调用setState()来重建小部件。 - Kirill Karmazin
@Aqib...但这更多是理论问题,因为首先您不希望使用仅依赖于参数的Statful小部件,最好使用简单的Stateless小部件。其次,当您的数据尚未准备好时,最好根本不显示小部件。 - Kirill Karmazin
1
@kirill-karmazin,你能详细说明一下无状态小部件解决方案吗?你会使用什么替代品?这是Flutter团队的最佳实践吗?谢谢。 - camillo777
显示剩余2条评论

18

为了传递初始值(而不向构造函数传递任何内容)

class MyStateful extends StatefulWidget {
  final String foo;

  const MyStateful({Key key, this.foo}): super(key: key);

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

class _MyStatefulState extends State<MyStateful> {
  @override
  void initState(){
    super.initState();
    // you can use this.widget.foo here
  }

  @override
  Widget build(BuildContext context) {
    return Text(foo);
  }
}

你可以在这里使用 this.widget.foo。 非常被低估的信息。感谢这个提示。 - elfico

5

Flutter的有状态小部件API相当尴尬:将数据存储在小部件中,以便在build()方法中访问它,该方法位于State对象中。如果您不想使用一些更大的状态管理选项(Provider、BLoC),请使用flutter_hooks(https://pub.dev/packages/flutter_hooks)——它是有状态小部件的更好、更清洁的替代品:

class Counter extends HookWidget {
  final int _initialCount;

  Counter(this._initialCount = 0);
  
  @override
  Widget build(BuildContext context) {
    final counter = useState(_initialCount);

    return GestureDetector(
      // automatically triggers a rebuild of Counter widget
      onTap: () => counter.value++,
      child: Text(counter.value.toString()),
    );
  }
}

3
最佳实践是将有状态的widget类定义为不可变,这意味着将所有依赖项(包括到达参数)定义为final参数,并通过在state类中使用widget.<fieldName>来访问它们。如果你想要改变它们的值,比如重新分配,你应该在你的state类中定义相同类型的属性,并在initState函数中重新分配它们。强烈建议不要在您的有状态widget类中定义任何非final属性并使其成为一个可变的类。像这样的模式:
class SomePage extends StatefulWidget{
  final String? value;
  SomePage({this.value});

  @override
  State<SomePage> createState() => _SomePageState();
}

class _SomePageState extends State<SomePage> {
  String? _value;

  @override
  void initState(){
    super.initState();
    setState(() {
      _value = widget.value;
    });
  }

 @override
 Widget build(BuildContext context) {
    return Text(_value);
 }
}

initState()中使用setState()是不正确的。只有在稍后更新状态时才应使用setState():https://dev59.com/L1QJ5IYBdhLWcg3wqnwT - Peter Bruins

1
@Rémi Rousselet,@Sanjayrajsinh和@Daksh Shah也很不错。但我也在起始点定义了这个问题,即哪个参数是哪个值。
   import 'package:flutter/material.dart';
    
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      String name = "Flutter Demo";
      String description = "This is Demo Application";
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: MainActivity(
            appName: name,
            appDescription: description,
          ),
        );
      }
    }
    
    class MainActivity extends StatefulWidget {
      MainActivity({Key key, this.appName, this.appDescription}) : super(key: key);
      var appName;
      var appDescription;
    
      @override
      _MainActivityState createState() => _MainActivityState();
    }
    
    class _MainActivityState extends State<MainActivity> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.appName),
          ),
          body: Scaffold(
            body: Center(
              child: Text(widget.appDescription),
            ),
          ),
        );
      }
    }

-1

要将数据传递给有状态的小部件,首先创建两个页面。现在从第一个页面打开第二个页面并传递数据。

class PageTwo extends StatefulWidget {
  final String title;
  final String name;

  PageTwo ({ this.title, this.name });

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

class PageTwoStateState extends State<PageTwo> {
  @override
  Widget build(BuildContext context) {
      return Text(
         widget.title,
         style: TextStyle(
               fontSize: 18, fontWeight: FontWeight.w700),
               ),
  }
}

class PageOne extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialButton(
          text: "Open PageTwo",
          onPressed: () {
                var destination = ServicePage(
                   title: '<Page Title>',
                   provider: '<Page Name>',
                );
                Navigator.push(context,
                    MaterialPageRoute(builder: (context) => destination));
                        },);
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接