likes
comments
collection
share

关于Provider的思考

作者站长头像
站长
· 阅读数 19

软件做三件事情

  1. 数据如何流转。对数据(本地和远程)的增删改查,以及内存数据落盘(包括本地存储设备以及远程存储设备)。
  2. 命令如何传递。对数据的操作(增删改查)以及页面的跳转。
  3. 数据如何展示。页面的跳转,不同的页面展示不同的业务功能,不同的形式的数据。

状态管理

状态管理:显示给用户的数据的管理。数据分为两种:一种是瞬时数据(保存在内存,不会落盘,应用重启数据丢失);另一种落盘数据,应用重启时候数据不会丢失,可以重新加载。两种数据类型仅仅是因为产品定义的不同导致的。例如:一个电商应用,有用户中心,购物车,首页三个标签,如果需要记录保存用户选中标签页索引,直接落盘保存,下次重启应用就直接显示选中的标签页;否则,就是瞬时数据。

命令式UI和声明式UI在UI更新方面的不同之处

之前做Android原生开发时候更新UI的方式:findViewById或者直接使用保存的属性变量,然后调用相关的方法进行设置属性来改变UI。原来的View一直不改变,只是改变相应的属性。

Flutter声明式UI更新方式:Widget组件是不可变的,而且是轻量级的。改变UI会创建新的Widget组件实例。 所以在 Flutter 中有这么一种说法: UI = f(state):

关于Provider的思考

状态管理的优势: 让开发者摆脱组件的繁琐控制,聚焦于状态处理。

InheritedWidget

幸运的是Flutter在widget中存在一种机制,能够为其子孙节点提供数据和服务。(换言之,不仅仅是它的子节点,所有在它下层的widget都可以)。就像你所了解的, Flutter中的一切都是widget。这里的机制也是一种widget—InheritedWidget, InheritedNotifier, InheritedModel等等。InheritedWidget这种机制就是Provider的基础。InhritedWidget底层使用了我们经常讨论的观察者数据模式,被观察者数据有变化的时候通知观察者update。

Provider简介

Provider是对InheritedWidget组件的上层封装,使其更易用,更易复用。让ChangeNotifier来处理数据,从而减少InheritedWidget大量的模版代码。 Talk is cheap. Show me the code.示例代码如下:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

/// This is a reimplementation of the default Flutter application using provider + [ChangeNotifier].

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => Counter(),
      child: const MyApp(),
    ),
  );
  // runApp(
  //   /// Providers are above [MyApp] instead of inside it, so that tests
  //   /// can use [MyApp] while mocking the providers
  //   MultiProvider(
  //     providers: [
  //       ChangeNotifierProvider(create: (_) => Counter()),
  //     ],
  //     child: const MyApp(),
  //   ),
  // );
}

/// Mix-in [DiagnosticableTreeMixin] to have access to [debugFillProperties] for the devtool
// ignore: prefer_mixin
class Counter with ChangeNotifier, DiagnosticableTreeMixin {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }

  /// Makes `Counter` readable inside the devtools by listing all of its properties
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(IntProperty('count', count));
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Example'),
      ),
      body: const Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),

            /// Extracted as a separate widget for performance optimization.
            /// As a separate widget, it will rebuild independently from [MyHomePage].
            ///
            /// This is totally optional (and rarely needed).
            /// Similarly, we could also use [Consumer] or [Selector].
            Count(),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        key: const Key('increment_floatingActionButton'),

        /// Calls `context.read` instead of `context.watch` so that it does not rebuild
        /// when [Counter] changes.
        onPressed: () => context.read<Counter>().increment(),
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

class Count extends StatelessWidget {
  const Count({super.key});

  @override
  Widget build(BuildContext context) {
    return Text(
      /// Calls `context.watch` to make [Count] rebuild when [Counter] changes.
      '${context.watch<Counter>().count}',
      key: const Key('counterState'),
      style: Theme.of(context).textTheme.headlineMedium,
    );
  }
}

  1. ChangeNotifier:被监听数据应该继承此类,数据更新时候需要通知监听者。
  2. ChangeNotifierProvider:它具有与ChangeNotifier相比的更改。子小组件可以访问状态对象并侦听更改。
  3. Consumer:在侦听状态更改时重建子树的一部分。最好能把 Consumer放在widget树层级尽量低的位置上,Consumer包裹的widget遇到ChangeNotifier数据变化的时候,都会重新构建Consumer包裹的widget树,如果层级过高,频繁更新会引起性能问题���
  4. Provider.of:允许后代小组件访问状态对象。能够监听变化,同时还能更改ChangeNotifier中的数据。
  5. MultiProvider能够通过组合ChangeNotifier的数组来提供多种类型的数据。

Provider的优缺点

优点:

  1. 使用简单。模型类继承ChangeNotifier,没有更多的布局widget,只需要通过context.read/context.watch操作或者监听模型类即可;
  2. 颗粒度把控简单。为了解决widget重新build太频繁的问题,官方推出了context.select来监听对象的部分属性。也可使用Consumer/Selector进行布局;
  3. 基于官方InheritedWidget的封装,不存在任何风险,很稳定且不会给性能方面加负担。

缺点:BuildContext强关联,BuildContext大多时候基本都是在widget中才能获取到,在其他地方想随时获取BuildContext是不切实际的。难以处理大型应用中的复杂状态;不支持异步操作;不支持共享状态跨widget树(当然使用ChangeNotifierProvider.value()方法来复用ChangeNotifierProvider实例,常驻内存达到跨widget的目的,但是会引起其他问题)。

总结

Provider是一个简洁优秀的状态管理框架,以后有很多优秀的状态管理框架都有Provider的影子,在 BLoC 与 Mobx 架构中使用 provider。Get管理框架也是一个非常实用的状态管理框架,全局单例(不受BuildContext影响),任意位置可以存取;存在类型重复,内存回收问题。希望我的文章对各位都有帮助,如果有问题可以不吝指教,祝好。

转载自:https://juejin.cn/post/7385776247601414154
评论
请登录