Flutter:将有状态的小部件组合到 flutter-redux 应用程序架构中

Flutter: combining stateful widgets into a flutter-redux app architecture

问题之后,我理解了为什么 StatefulWidgetsRedux-based flutter app 的上下文中有意义.这也是我想要完成的-

一个页面,该页面从应用范围内的状态(登录用户详细信息、api 数据等)“馈送”信息,还可以从其连接的 ViewModel 中调度操作,但还包含有状态的具有较小范围、短暂状态的小部件。诸如动​​画之类的东西,在提交之前即时验证用户输入,以及根据用户操作更改 UI。

所以我很感兴趣,希望这里有人能帮助我。 目前我所有的“页面”都是通过商店连接到应用程序状态的无状态小部件,方式如下:

class LoginPage extends StatelessWidget {
  final String TAG = "LoginPage";
  bool isGreen = false;

  LoginPage({Key key}) : super(key: key);

  void changeColor() {
    isGreen = !!isGreen;
  }


  @override
  Widget build(BuildContext context) {
    /// [StoreConnector] is used to convert store data (using the fromStore)
    /// into a ViewModel suitable for the page.
    return StoreConnector<AppState, LoginPageViewModel>(
        builder: (context, viewModel) {
          return Scaffold(
              body: Column(
               children: <Widget>[
                Container(...),
                Text(
                  text: viewModel.some_value_from_the_store,
                  color: isGreen ? Colors.green : Colors.red,
                ),
                ElevatedButton(
                  onPressed: () => changeColor(),
                  child: Text('Press to change color'),
                )
            ],
          ));
        },
        converter: LoginPageViewModel.fromStore);
  }
}

这里我只是尝试根据用户点击简单地更改“LoginPage”小部件内的文本颜色,同时仍保持与商店的连接,以便 UI 不断更新新应用程序-状态信息到达。

我们那里有这样的参考资料吗?任何人都可以提供一个例子,或者只是如何实现这一目标的基本指导方针吗? 看起来很简单,但我正在努力。

以下是一些可能有用的想法。

您的小部件需要是有状态的。您正在尝试跟踪随时间变化的颜色。无状态小部件仅允许最终属性 - 即您只能在创建时启动它们的值。

您的变量 isGreen 和方法 changeColor() 应该是状态对象的一部分。您的 build() 方法也将在那里。

接下来 - 当您调用您的方法时,您应该调用:

void changeColor() {
    setState(() {isGreen = !isGreen;});
}

我认为在您的代码中您没有翻转值(=!!iGreen 与 =isGreen 相同)。但更重要的是——您并没有告诉框架应该重建您的小部件。 您可以自己测试一下:单击一次(在更正“!!”之后)。什么都不会发生。如果您在模拟器中强制刷新 - 您应该会看到颜色确实发生了变化。这是因为你手动刷新了它。 setState() 为您完成:它将 运行 您提供的代码,然后调用 Flutter 告诉它刷新您的小部件。看 - flutter 没有神奇的触发器,并且不会观察您的代码来决定何时刷新您的小部件。 setState() 告诉它这样做。

作为一般规则:您的应用程序状态 - 即与多个小部件(或页面)共享的数据应该是 'lifted up' 小部件树,并保存在 Provider classes 中。

有状态的小部件应该只保留与它们直接相关的数据,并且只保留与它们相关的数据。通常,这是有助于呈现提供者数据的数据。例如: -提供商将保留您显示的项目列表。您的有状态小部件将跟踪当前选定的项目 -Provider 保留要显示的文本。您的有状态小部件保留字体大小、字体颜色等,允许用户在该特定小部件上更改它,但不能在使用相同数据的所有小部件上更改。

在您的示例中 - 您的云在屏幕上有多个登录小部件(出于某种原因)。在这种情况下: -如果你想让你所有的登录小部件改变颜色 - 在你的供应商中保持 isGreen class。在这种情况下,您的小部件可以是无状态的。 -如果您只想让您点击的小部件更改颜色 - 这属于您的有状态小部件,因为没有其他人关心这个值。

让我用您想要的代码来更新它。将小部件连接到商店的方式与有状态和无状态小部件的工作方式相同。

注意 - 我注释掉了您连接到商店的代码,只是为了展示您的小部件将如何改变颜色。取消注释商店代码,您就可以开始了。

您可以在 https://dartpad.dev/ 中快速 运行,只需 copy/paste 代码。

// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: LoginPage(),
    );
  }
}

class LoginPage extends StatefulWidget {
  final String TAG = "LoginPage";
  LoginPage({Key key}) : super(key: key);
  
  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  
  bool isGreen = false;

  void changeColor() {
    setState(() {
      isGreen = !isGreen;
    });
    
  }

  @override
  Widget build(BuildContext context) {
    /// [StoreConnector] is used to convert store data (using the fromStore)
    /// into a ViewModel suitable for the page.
    //return StoreConnector<AppState, LoginPageViewModel>(
        //builder: (context, viewModel) {
          return Scaffold(
              body: Column(
               children: <Widget>[
                Container(),
                Text(
                  'viewModel.some_value_from_the_store',
                  style: TextStyle(color: isGreen ? Colors.green : Colors.red),
                ),
                ElevatedButton(
                  onPressed: () => changeColor(),
                  child: Text('Press to change color'),
                )
            ],
          ));
        //},
        //converter: LoginPageViewModel.fromStore);
  //}
}
}