35

(译)Flutter+Redux- 如何做一个购物清单应用

 5 years ago
source link: http://jinyulei.cn/flutter/state/management/2019/04/21/Flutter-+-Redux-How-to-make-Shopping-List-App/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

原文链接

嗨,大家好!在本文中,我想向你展示如何使用Redux创建Flutter应用程序。如果你不知道Flutter是什么,我推荐你阅读我的文章 Flutter - 你可能喜欢它的5个理由 。但是,如果你知道Flutter是什么,并且你想要创建一个设计良好,易于测试且具有非常可预测的行为的应用程序 - 那么请继续看下去!

什么是Redux?

首先,让我们首先解释一下Redux是什么。Redux是一种应用程序架构,最初是为JavaScript构建的,现在用于使用反应式框架构建的应用程序(例如React Native或Flutter)。Redux是由Facebook制作的Flux架构的简化版本。但Redux有什么用呢?基本上,你需要知道三件事:

action

听起来很酷,但该解决方案的优点是什么?

  • 我们控制了状态 - 这意味着我们确切地知道导致状态变化的原因,我们没有重复状态,我们可以轻松地跟踪数据流
  • 纯reducer方法很容易对其进行测试 -我们可以传入状态和 action ,然后看结果是否正确
  • 应用程序结构清晰 - 我们为 actionmodel ,业务逻辑等提供了不同的层 - 因此你要加新功能的时候,你会知道要放在哪里。
  • 对于更复杂的应用程序来说,这是一个很棒的架构 - 您不需要将整个视图树中的状态从父级传递到子级
  • 还有一个……

Redux时间旅行

Redux中有一个很酷的功能 - :tada:时间旅行!使用Redux和适当的工具,您可以随时跟踪应用程序状态,检查实际状态并随时重新创建。看看这个功能到底是什么样的:

zYrqIjA.gif

Redux相关Widget在一个简单例子上的应用

所有上述规则使数据在Redux是单向流动的。但是这是什么意思?事实上,这都是由多个 action ,多个 reducer , store 以及多个 state 完成的。让我们想象显示按钮计数器的应用程序:

qem6Vfq.png!web

action
reducer

正如你能看到的,通常都是与状态相关的。你有单个应用程序状态,状态对于视图是只读的,并且要创建新的状态的时候你需要发送一个 action 。发送 action 会触发 reducer 创建并发送一个新的应用状态。循环往复。

6N36Jbm.png!web Redux数据流

使用Redux的购物清单应用示例

让我展示一下Redux在更为复杂例子中的实践。我们将创建一个简单的购物车应用程序。在这个应用程序中,将具有以下功能:

  • 添加条目
  • 将条目标记为已选中
  • 就那么多

该应用程序的样子是这样的:

vU7R3my.png!web 你可以在Github上看到 该程序的完整代码

让我们从编码开始吧! :point_down:

先决条件

在本文中,我不会展示应用程序UI创建的部分。您可以查看这个 实现Redux之前的Shopping List应用程序的代码 。我们将从这个基础上,把Redux添加到此应用程序中。

如果你之前从未使用过Flutter,我建议您尝试使用Google推出的 Flutter Codelabs

设置

要在Flutter上使用Redux运行,您需要向pubspec.yaml文件添加依赖项:

flutter_redux: ^0.5.2

您可以在 flutter_redux 包页面上查看最新版本。

模型(Model)

我们的应用程序需要管理添加和修改条目,因此我们将使用简单的CartItem模型来存储单个条目的状态。我们的整个应用程序状态将只是多个CartItem组成的列表。如你所见,CartItem只是一个普通的Dart对象

class CartItem {
  String name;
  bool checked;

  CartItem(this.name, this.checked);
}

注意:这里是这个文件的完整源代码

Actions

首先,我们需要声明 action 。 Action基本上是可以被调用以更改应用程序状态的任何意图。在我们的应用程序中,我们将有两个 action ,用于添加和修改条目:

class AddItemAction {
  final CartItem item;

  AddItemAction(this.item);
}

class ToggleItemStateAction {
  final CartItem item;

  ToggleItemStateAction(this.item);
}

注意:这里是这个文件的完整源代码

Reducers

然后,我们需要告诉我们的应用程序应该如何处理这些操作。这就是Reducer的用途 - 它们只是采用当前的 Stateaction ,然后它们会创建并返回新的 State 。我们将有两种 Reducer 方法:

List<CartItem> appReducers(List<CartItem> items, dynamic action) {
  if (action is AddItemAction) {
    return addItem(items, action);
  } else if (action is ToggleItemStateAction) {
    return toggleItemState(items, action);
  } 
  return items;
}

List<CartItem> addItem(List<CartItem> items, AddItemAction action) {
  return List.from(items)..add(action.item);
}

List<CartItem> toggleItemState(List<CartItem> items, ToggleItemStateAction action) {
  return items.map((item) => item.name == action.item.name ?
    action.item : item).toList();
}

注意:这里是这个文件的完整源代码

方法 appReducers()action 委托给正确的方法。 addItem()和toggleItemState()方法都返回新列表-这个列表就是我们的新应用程序状态。如你所见,您不应修改当前列表,而是每次都创建新的列表。

StoreProvider

现在,当我们有了 actionreducer 时,我们需要提供存储应用程序状态的位置。它在Redux中称为仓库,它是我们应用程序的唯一数据源。

void main() {
  final store = new Store<List<CartItem>>(
      appReducers,
      initialState: new List());

  runApp(new FlutterReduxApp(store));
}

注意:这里是这个文件的完整源代码

要创建仓库,我们需要传递 reducers 方法和初始应用程序状态。如果我们创建了仓库,我们必须将它传递给 StoreProvider ,来告诉我们的应用程序它可以被所有请求应用状态的对象使用:

class FlutterReduxApp extends StatelessWidget {
  final Store<List<CartItem>> store;

  FlutterReduxApp(this.store);

  @override
  Widget build(BuildContext context) {
    return new StoreProvider<List<CartItem>>(
      store: store,
      child: new ShoppingCartApp(),
    );
  }
}

注意:这里是这个文件的完整源代码

在上面的示例中,ShoppingCartApp()是主要的应用程序widget。

StoreConnector

目前我们拥有除了实际添加和更改条目之外的所有内容都已经做了。那么怎么添加和修改条目呢,我们需要使用 StoreConnector 。这是一种获取仓库并采取一些行动或读取它的状态的方法。

首先,我们想要读取当前数据并在列表中显示:

class ShoppingList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new StoreConnector<List<CartItem>, List<CartItem>>(
      converter: (store) => store.state,
      builder: (context, list) {
        return new ListView.builder(
            itemCount: list.length,
            itemBuilder: (context, position) =>
                new ShoppingListItem(list[position]));
      },
    );
  }
}

注意:这里是这个文件的完整源代码

上面的代码使用 StoreConnector 包装默认的 ListView.builderStoreConnector 可以获取当前应用程序状态(List )并将其与转换器方法映射到任何内容。出于这种情况的目的,它将是相同的状态(List ),因为我们需要这里的整个列表。

接下来,在builder方法中我们获取到了列表 - 这基本上是来自仓库的CartItems列表,我们可以使用它来构建ListView。

好的,很酷 - 我们到读取数据这一步了。现在如何去设置一些数据?

为此,我们还将使用 StoreConnector ,但方式略有不同。

class AddItemDialog extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new StoreConnector<List<CartItem>, OnItemAddedCallback>(
        converter: (store) {
      return (itemName) =>
          store.dispatch(AddItemAction(CartItem(itemName, false)));
    }, builder: (context, callback) {
      return new AddItemDialogWidget(callback);
    });
  }
}
typedef OnItemAddedCallback = Function(String itemName);

注意:这里是这个文件的完整源代码

我们来看看代码吧。我们使用了 StoreConnector ,就像前面的例子一样,但这一次,我们将把它映射到OnItemAddedCallback,而不是将CartItems列表映射到同一个列表中。这样我们就可以将回调传递给AddItemDialogWidget,并在用户添加一些新项时调用它:

class AddItemDialogWidgetState extends State<AddItemDialogWidget> {
  String itemName;

  final OnItemAddedCallback callback;
  AddItemDialogWidgetState(this.callback);

  @override
  Widget build(BuildContext context) {
    return new AlertDialog(
      ...
      actions: <Widget>[
        ...
        new FlatButton(
            child: const Text('ADD'),
            onPressed: () {
              ...
              callback(itemName);
            })
      ],
    );
  }
}

注意:这里是这个文件的完整源代码

现在,每次用户按“ADD”按钮时,回调都会发送AddItemAction()事件。

现在,我们可以为切换条目状态做类似的事情:

class ShoppingListItem extends StatelessWidget {
  final CartItem item;

  ShoppingListItem(this.item);

  @override
  Widget build(BuildContext context) {
    return new StoreConnector<List<CartItem>, OnStateChanged>(
        converter: (store) {
      return (item) => store.dispatch(ToggleItemStateAction(item));
    }, builder: (context, callback) {
      return new ListTile(
        title: new Text(item.name),
        leading: new Checkbox(
            value: item.checked,
            onChanged: (bool newValue) {
              callback(CartItem(item.name, newValue));
            }),
      );
    });
  }
}

typedef OnStateChanged = Function(CartItem item);

注意: 这里是这个文件的完整源代码

与前面的示例一样,我们使用 StoreConnector 将List 映射到OnStateChanged回调。现在每次更改复选框(在onChanged方法中),回调都会触发ToggleItemStateAction事件。

摘要

以上就是全部内容!在本文中,我们使用Redux架构创建了一个简单的购物清单应用程序。在我们的应用程序中,我们可以添加一些条目并更改其状态。向此应用程序添加新功能就像添加新action和reducer一样简单。

在这里 ,您可以查看此应用程序的完整源代码,包括时间旅行widge:

希望你喜欢这篇文章并敬请期待更多! :raised_hands:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK