Flutter状态管理官方文档笔记

状态管理是flutter学习过程中很重要的部分,能够深入的理解局部状态和全局状态,那么在以后的flutter学习和生产项目开发中会更得心应手。

开始以声明的方式思考(Start thinking declaratively)

在Flutter中可以重头开始重建部分UI而不是修改它。

如果需要,即使在每一帧上,Flutter都足够的快。

Flutter是声明式的。这就意味着Flutter构建其用户界面以反映应用程序的当前状态。

image

区分短暂状态和应用状态

广义上来说,应用程序状态是应用程序运行时在内存中存在的所有内容。

但这里包括应用程序的资源,Fllutter框架保留有关UI的所有变量,动画状态,纹理,字体等。

首先,你甚至不管理某些状态(like textures),框架帮助你去处理。

更有用的状态定义是“在任何时刻重建UI时所需要的所有数据”。

其次,你自己管理的状态可分为两种概念类型:临时状态和应用状态。

临时状态

应用状态

状态不是短暂的,你希望在应用程序的许多部分共享,并且你希望在用户会话之间保持-这就是我们说的应用程序状态(有时也称为共享状态)。

应用程序状态的示例:

  • 用户偏好
  • 登录信息
  • 社交网络中的通知
  • 电子商务应用中的购物车
  • 新闻应用中文章的已读/未读状态

要管理应用状态,你想要研究你的选项。

没有明确的规则

没有明确的规则来区分特定变量是短暂的还是应用状态。

有时,你需要将一个更改为另一个。例如,你从一些明显的短暂状态开始,随着你的应用程序在功能中的增长,它需要移动到应用程序状态。

image

总之,在Flutter中任何应用都有这两种状态的概念。临时状态可以使用setate和setState()来实现,并且通常是单个小部件的本地状态。剩下的就是应用程序状态。这两种类型在任何Flutter应用程序中都占有一席之地,两者之间的分割取决于你自己的偏好和应用程序的复杂性。

应用示例

这个应用有三个页面:登录提示,目录和购物车(分别由MyLoginScreen,MyCatalog和MyCart Widgets表示)。


image

提升状态

在Flutter中,将状态保持在widgets外部并使用状态是有意义的。

在像Flutter这样声明框架中,如果你想改变UI,则必须去重建它。没有简单的方法可以使用MyCart.updateWith(somethingNew)。换句话说,通过在其上调用方法,很难从外部强制更改widgets。即使你能够使其实现,框架也将会处处为难你而不是帮助你。

//BAD: DO NTO DO THIS
void myTapHandler() {
  var cartWidget = somehowGetMyCartWidget();
  cartWidget.updateWith(item);
}
//BAD: DO NTO DO THIS
Widget build(BuildContext context) {
  return SomeWidget(
    //The initial state of the cart.
    );
}

void updateWith(Item item) {
  //Somehow you need to change the UI from here.
}

在Flutter中,每次更改内容时,都会创建一个新的Widgets。

//GOOD
void myTapHandler(BuildContext context) {
  var cartModel = somehowGetMyCartWidget(context);
  cartModel.add(item);
}

现在,MyCart只有一个代码路径来构建任何版本的UI。

//GOOD
Widget build(BuildContext context) {
  var cartModel = somehowGetMyCartWidget(context);
  return SomeWidget(
    //Just construct the UI once, using the current state of the cart.
    //---
    )
}

在我们的实例中,内容需要存在于MyApp中。当他发生改变时,它都会从上面重建。因此,MyCart不需要担心生命周期-它只是声明了为任何给定内容显示的内容。当更改时,旧的MyCart Widget将消失,并完全被新的Widget取代。

image

这就是为什么我们说的Widgets是一成不变的。它们没有改变-只是被取代了。

访问状态

当你点击目录中的一个items时,添加到购物车。但购物车运行在MyListItem之外,我们该如何解决?

一个简单的选项是提供MyListItem单击时可以调用的回调。

@override
Widget build(BuildContext context) {
  return SomeWidget(
    // Construct the widget, passing it a reference to the method above.
    MyListItem(myTapCallback),
  );
}

void myTapCallback(Item item) {
  print('user tapped on $item');
}

ChangeNotifier

ChangeNotifier是Flutter SDK中的一个简单的类,它向其侦听器提供更改通知。类似于Obserable。

在提供程序中,ChangeNotifier是封装应用程序状态的一种方法。对于非常简单的应用程序,你可以使用一个ChangeNotifier。在复杂的模型中,你将有几个模型,因此有几个ChangeNotifiers。

在这个购物应用实例中,我们想在ChangeNotifier管理购物车的状态。我们创建一个继承与它的类,就如下:

class CartModel extends ChangeNotifier {
  ///Internal, private state of the cart.
  final List<Item> _items = [];

  /// An unmodifiable view of the times in the cart.
  UnmodifiableListView<Item> get items => UnmodifiableListView(_items);

  /// The current total price of all times (assuming all items cost $42).
  int get totalPrice => _items.length * 42;

  /// Add [item] to cart. This is the only way to modify the cart from outside.
  void add(Item item) {
    _items.add(item);
    // This call tells the widgets that are listening to this model to rebuild.
    notifyListeners();
  }
}

ChangeNotifierProvider

ChangeNotifierProvider是一个widget,它为其后代提供ChangeNotifier的实例。它来自provider package。

void main() {
  runApp(
    ChangeNotifierProvider(
      builder: (context) => CartModel(),
      child: MyApp(),
    ),
  );
}

If you want to provide more than one class, you can use MultiProvider:

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(builder: (context) => CartModel()),
        Provider(builder: (context) => SomeOtherClass()),
      ],
      child: MyApp(),
    ),
  );
}

Consumer

现在CartModel通过顶部的ChangeNotifierProvider声明提供给我们应用程序中的widget,我们可以开始使用它。

return Consumer<CartModel>(
  builder: (context, cart, child) {
    return Text("Total price: ${cart.totalPrice}");
  },
);

我们必须指定我们想要访问model的类型。这里我们指定的CartModel。

第三个参数child,在这里用于优化。如果你的Consumer下有一个大widget子树,模型更改时不会更改,你可以构造一次并通过构建器获取它。

return Consumer<CartModel>(
  builder: (context, cart, child) => Stack(
        children: [
          // Use SomeExpensiveWidget here, without rebuilding every time.
          child,
          Text("Total price: ${cart.totalPrice}"),
        ],
      ),
  // Build the expensive widget here.
  child: SomeExpensiveWidget(),
);

最好的做法是将Consumer widget尽可能的深入树中。你不希望重建UI的大部分内容只是因为某些细节发生了变化。

// DON'T DO THIS
return Consumer<CartModel>(
  builder: (context, cart, child) {
    return HumongousWidget(
      // ...
      child: AnotherMonstrousWidget(
        // ...
        child: Text('Total price: ${cart.totalPrice}'),
      ),
    );
  },
);

Instead:

// DO THIS
return HumongousWidget(
  // ...
  child: AnotherMonstrousWidget(
    // ...
    child: Consumer<CartModel>(
      builder: (context, cart, child) {
        return Text('Total price: ${cart.totalPrice}');
      },
    ),
  ),
);

Provider.of

有时,你并不真正需要模型中的数据来更改UI,但你仍需要访问它。例如,ClearCart按钮希望允许用户从购物车中删除所有内容。它不需要显示购物车的内容,只需要调用clear()方法即可。

对于此用例,我们可以使用Provider.of,并将listen参数设置为false。

 Provider.of<CartModel>(context, listen: false).add(item);

在调用notifiyListeners时,在构建方法中使用上述行不会导致此窗口小部件重建。

把它们放在一起

当你准备玩provider时,不要忘记首先将它的依赖项添加到pubspec.yaml。

name: my_name
description: Blah blah blah.

# ...

dependencies:
  flutter:
    sdk: flutter

  provider: ^3.0.0

dev_dependencies:
  # ...
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容