前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Flutter 技能篇】你不得不会的状态管理 Provider

【Flutter 技能篇】你不得不会的状态管理 Provider

作者头像
政采云前端团队
发布2021-01-28 10:14:37
3.6K0
发布2021-01-28 10:14:37
举报
文章被收录于专栏:采云轩

本文首发于政采云前端团队博客:【Flutter 技能篇】你不得不会的状态管理 Provider https://www.zoo.team/article/flutter-and-provider

前言

Provider,Google 官方推荐的一种 Flutter 页面状态管理组件,它的实质其实就是对 InheritedWidget 的包装,使它们更易于使用和重用。关于 InheritedWidget 不做过多介绍,本篇文章主要较全面地介绍 Provider 的相关用法,能在业务场景中有所运用。

本篇文章示例源码:https://github.com/xiaomanzijia/FlutterProvider

用法

Step1:添加依赖

代码语言:javascript
复制
dependencies:
  flutter:
    sdk: flutter
  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  provider: ^4.0.4

Step2:观察结构

执行 flutter pub get 后,可以在工程看到 provider 的 sdk 源码,结构如下:

Step3:示例简介

本示例将讲解 Provider 基础组件的使用,包括但不限于 ChangeNotifier, NotifierProvider, Consumer, Selector, ProxyProvider, FutureProvider, StreamProvider。

Step4:创建一个 ChangeNotifier

我们先新建一个 Model1,继承 ChangeNotifier,使之成为我们的数据提供者之一。

代码语言:javascript
复制
class Model1 extends ChangeNotifier {
  int _count = 1;
  int get count => _count;
  set count(int value) {
    _count = value;
    notifyListeners();
  }
}

追踪 ChangeProvider 源码,我们发现它并不属于 Provider,它其实是定义在 Flutter SDK foundation 下面的 change_provider.dart 文件。ChangeNotifier 实现了 Listenable 抽象类,里面维护了一个 ObserverList。 Listenable 类源码:

代码语言:javascript
复制
abstract class Listenable {
  const Listenable();
  factory Listenable.merge(List<Listenable> listenables) = _MergingListenable;
  void addListener(VoidCallback listener);
  void removeListener(VoidCallback listener);
}

可以看出,主要提供了 addListener 和 removeListener 两个方法。 ChangeNotifier 类源码:

代码语言:javascript
复制
class ChangeNotifier implements Listenable {
  ObserverList<VoidCallback> _listeners = ObserverList<VoidCallback>();
  bool _debugAssertNotDisposed() {
    assert(() {
      if (_listeners == null) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('A $runtimeType was used after being disposed.'),
          ErrorDescription('Once you have called dispose() on a $runtimeType, it can no longer be used.')
        ]);
      }
      return true;
    }());
    return true;
  }
  @protected
  bool get hasListeners {
    assert(_debugAssertNotDisposed());
    return _listeners.isNotEmpty;
  }
  void addListener(VoidCallback listener) {
    assert(_debugAssertNotDisposed());
    _listeners.add(listener);
  }
  @override
  void removeListener(VoidCallback listener) {
    assert(_debugAssertNotDisposed());
    _listeners.remove(listener);
  }
  @mustCallSuper
  void dispose() {
    assert(_debugAssertNotDisposed());
    _listeners = null;
  }
  @protected
  @visibleForTesting
  void notifyListeners() {
    assert(_debugAssertNotDisposed());
    if (_listeners != null) {
      final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners);
      for (VoidCallback listener in localListeners) {
        try {
          if (_listeners.contains(listener))
            listener();
        } catch (exception, stack) {
          FlutterError.reportError(FlutterErrorDetails(
            exception: exception,
            stack: stack,
            library: 'foundation library',
            context: ErrorDescription('while dispatching notifications for $runtimeType'),
            informationCollector: () sync* {
              yield DiagnosticsProperty<ChangeNotifier>(
                'The $runtimeType sending notification was',
                this,
                style: DiagnosticsTreeStyle.errorProperty,
              );
            },
          ));
        }
      }
    }
  }
}

除了实现 addListener 和 removeListener 外,还提供了 dispose 和 notifyListeners 两个方法。Model1 中,当我们更改 count 值时,就会调用 notifyListeners 方法通知 UI 更新。

Step5:创建 ChangeNotifierProvider

示例简介

方式一:通过 ChangeNotifierProvider
代码语言:javascript
复制
return ChangeNotifierProvider(
      create: (context) {
        return Model1();
      },
      child: MaterialApp(
        theme: ArchSampleTheme.theme,
        home: SingleStatsView(),
      ),
);

这里通过 ChangeNotifierProvider 的 create 把 ChangeNotifier(即 Model1)建立联系,作用域的范围在 child 指定的 MaterialApp,这里我们将 SingleStatsView 作为首页,SingleStatsView 里面使用了 Model1 作为数据源。需要注意的是,不要把所有状态的作用域都放在 MaterialApp,根据实际业务需求严格控制作用域范围,全局状态多了会严重影响应用的性能。

代码语言:javascript
复制
class SingleStatsView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          FlatButton(
            color: Colors.blue,
            child: Text('Model1 count++'),
            onPressed: () {
              Provider.of<Model1>(context, listen: false).count++;
            },
          ),
          Padding(
            padding: const EdgeInsets.only(bottom: 8.0),
            child: Text('Model count值变化监听',
                style: Theme.of(context).textTheme.title),
          ),
          Padding(
            padding: const EdgeInsets.only(bottom: 8.0),
            child: Text('Model1 count:${Provider.of<Model1>(context).count}',
                style: Theme.of(context)
                    .textTheme
                    .subhead
                    .copyWith(color: Colors.green)),
          ),
        ],
      ),
    );
  }
}
方式二:通过 ChangeNotifierProvider.value
代码语言:javascript
复制
return ChangeNotifierProvider.value(
  value: Model1(),
  child: MaterialApp(
  theme: ArchSampleTheme.theme,
  home: SingleStatsView(),
));

可以看出,和方式一相差无几,方式一用的 create 创建的 ChangeNotifier,这里用的 value 创建。追溯 ChangeNotifierProvider 源码:

代码语言:javascript
复制
class ChangeNotifierProvider<T extends ChangeNotifier> extends ListenableProvider<T> {
  static void _dispose(BuildContext context, ChangeNotifier notifier) {
    notifier?.dispose();
  }
  
  ChangeNotifierProvider({
    Key key,
    @required Create<T> create,
    bool lazy,
    Widget child,
  }) : super(
          key: key,
          create: create,
          dispose: _dispose,
          lazy: lazy,
          child: child,
        );
        
  ChangeNotifierProvider.value({
    Key key,
    @required T value,
    Widget child,
  }) : super.value(
          key: key,
          value: value,
          child: child,
        );
}

Step6:在页面中监听状态变更,其他使用方式

示例

我们先了解下 ValueListenableBuilder,它可以监听指定值的变化进行 UI 更新,用法如下:

ValueNotifier

1.新建 ValueNotifier

代码语言:javascript
复制
final ValueNotifier<int> _counter = ValueNotifier<int>(0);

在 builder 方法将之指定到 Model1 的 count,这样当 Model1 中的 count 变化时 _counter 也能监听到。

代码语言:javascript
复制
_counter.value = Provider.of<Model1>(context).count;

2.关联 ValueListenableBuilder

ValueListenableBuilder 的 valueListenable 可以绑定一个 ValueNotifier,用于监听 ValueNotifier 的值变化。

代码语言:javascript
复制
ValueListenableBuilder(
  valueListenable: _counter,
  builder: (context, count, child) => Text(
  'ValueListenableBuilder count:$count'),
),

ValueNotifier 源码:

代码语言:javascript
复制
class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
  ValueNotifier(this._value);
  @override
  T get value => _value;
  T _value;
  set value(T newValue) {
    if (_value == newValue)
      return;
    _value = newValue;
    notifyListeners();
  }
  @override
  String toString() => '${describeIdentity(this)}($value)';
}

可以看出,ValueNotifer 也是继承 ChangeNotifier,并实现了 ValueListenable,特别之处是在 set value 的时候调用了 notifyListeners,从而实现了状态变更监听。

MultiProvider

示例简介

一旦业务场景复杂,我们的页面可能需要监听多个 ChangeNotifier 的数据源,这时候 MultiProvider 就派上用场了。该示例在 SingleStatsView 上进行了扩展,这里我们新建一个 MultiStatsView,监听 Model1 和 Model2 的数据变化。

Model2

代码语言:javascript
复制
class Model2 extends ChangeNotifier {
  int _count = 1;
  int get count => _count;
  set count(int value) {
    _count = value;
    notifyListeners();
  }
}

MultiStatsView

代码语言:javascript
复制
class MultiStatsView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          FlatButton(
            color: Colors.blue,
            child: Text('Model1 count++'),
            onPressed: () {
              Provider.of<Model1>(context, listen: false).count++;
            },
          ),
          FlatButton(
            color: Colors.blue,
            child: Text('Model2 count++'),
            onPressed: () {
              Provider.of<Model2>(context, listen: false).count++;
            },
          ),
          Padding(
            padding: const EdgeInsets.only(bottom: 8.0),
            child: Text('Model count值变化监听',
                style: Theme.of(context).textTheme.title),
          ),
          Padding(
            padding: const EdgeInsets.only(bottom: 8.0),
            child: Text('Model1 count:${Provider.of<Model1>(context).count}',
                style: Theme.of(context)
                    .textTheme
                    .subhead
                    .copyWith(color: Colors.green)),
          ),
          Padding(
            padding: const EdgeInsets.only(bottom: 24.0),
            child: Text('Model2 count:${Provider.of<Model2>(context).count}',
                style: Theme.of(context)
                    .textTheme
                    .subhead
                    .copyWith(color: Colors.red)),
          ),
        ],
      ),
    );
  }
}

使用 MultiProvider 关联 MultiStatsView,可以看出 MultiProvider 提供了 providers 数组,我们可以把 ChangeNotifierProvider 放进去。

代码语言:javascript
复制
return MultiProvider(
      providers: [
        ChangeNotifierProvider.value(value: Model1()),
        ChangeNotifierProvider.value(value: Model2()),
      ],
      child: MaterialApp(
        theme: ArchSampleTheme.theme,
        localizationsDelegates: [
          ArchSampleLocalizationsDelegate(),
          ProviderLocalizationsDelegate(),
        ],
        home: MultiStatsView(),
      ),
);

若针对 MultiStatsView 仅提供 Model1 关联的 ChangeNotifierProvider,你会看到如下报错:

代码语言:javascript
复制
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following ProviderNotFoundException was thrown building MultiStatsView(dirty, dependencies:
[_LocalizationsScope-[GlobalKey#48c61], _InheritedTheme, _DefaultInheritedProviderScope<Model1>]):
Error: Could not find the correct Provider<Model2> above this MultiStatsView Widget
To fix, please:
  * Ensure the Provider<Model2> is an ancestor to this MultiStatsView Widget
  * Provide types to Provider<Model2>
  * Provide types to Consumer<Model2>
  * Provide types to Provider.of<Model2>()
  * Ensure the correct `context` is being used.

这是因为 Model2 没有注册导致。

ProxyProvider

从 3.0.0 开始,开始提供 ProxyProviderProxyProvider 可以将其他 provider 的值聚合为一个新对象,并且将结果传递给 Provider。新对象会在其依赖的宿主 provider 更新后被更新。

新建一个 User 类

代码语言:javascript
复制
class User {
  var name;
  User(this.name);
}

我们把 Model1 通过 ProxyProvider 聚合成 User 对象,然后取 User 对象中的 name 进行渲染。

代码语言:javascript
复制
ProxyProvider<Model1, User>(
  update: (context, value, previous) => User("change value ${value.count}"),
  builder: (context, child) => Text(
  'ProxyProvider: ${Provider.of<User>(context).name}',
  style: Theme.of(context).textTheme.title),
)

可以看出,通过 ProxyProvider 方式,我们直接调用 Provider.of<User>(context) 取值,关联 User 的 Provider 我们并没有注册,也能有效运行。

FutureProvider

通过名字可以看出,这个 Provider 和异步执行有关,用法类似于 FutureBuilder。这里用 FutureProvider 模拟 2 秒后更新 Model1 的初始值。可以在 initialData 指定初始值,create 方法指定具体的异步任务,builder 方法中可以用 Provider.of 取出异步任务执行返回的值进行页面渲染。还可以定义 catchError 捕获异常,updateShouldNotify 比较新旧值是否 rebuild,新的 create/update 回调函数是懒加载的,也就是说它们在对应的值第一次被读取的时候才被调用,而非 provider 首次被创建时。如果不需要这个特性,可以将 lazy 属性值置为 false。

代码语言:javascript
复制
FutureProvider<Model1>(
  create: (context) {
  return Future.delayed(Duration(seconds: 2))
   .then((value) => Model1()..count = 11);
  },
  initialData: Model1(),
  builder: (context, child) => Text(
    'FutureProvider ${Provider.of<Model1>(context).count}',
    style: Theme.of(context).textTheme.title),
),
StreamProvider

通过名字可以看出,StreamProvider 也是一个异步执行有关的 Provider,用法类似于 StreamBuilder。这里用 StreamProvider 模拟每隔1秒更新 Model1 的初始值。其余参数和 FutureProvider 用法类似。

代码语言:javascript
复制
StreamProvider(create: (context) {
  return Stream.periodic(Duration(seconds: 1), (data) => Model1()..count = data);
},
  initialData: Model1(),
  builder: (context, child) => Text(
    'StreamProvider: ${Provider.of<Model1>(context).count}',
     style: Theme.of(context).textTheme.title),
),
Consumer

具体用法如下,builder 中的参数分别是 Context context, T value, Widget child,value 即 Model1,value 的类型和 Model1 类型一致,builder 方法返回的是 Widget,也就是被 Consumer 包裹的 widget,当监听的 model 值发生改变,此 widget 会被 Rebuild。

代码语言:javascript
复制
Consumer<Model1>(
  builder: (context, model, child) {
    return Text('Model1 count:${model.count}');
  },
)
Selector

可以看出,Selector 和 Consumer 很相似,唯一不同的是,Selector 可以自定义返回类型,如下 Selector,我们这里监听 Model1 中的 count 变化,所以这里返回类型定义为 Int 类型。其中 builder 方法中的参数分别是 Context context, T value, Widget child,这里的 value 的类型和 Selector 中定义的返回类型一致。builder 方法返回的是 Widget,也就是被 Selector 包裹的 widget,我们可以指定监听 ChangeNotifier 中的某个值的变化,从而可触发此 widget Rebuild。

代码语言:javascript
复制
Selector<Model1, int>(
  builder: (context, count, child) => Text(
    "Selector示例演示: $count",
    style: Theme.of(context).textTheme.title,
  ),
  selector: (context, model) => model.count,
),

源码:

代码语言:javascript
复制
class Selector<A, S> extends Selector0<S> {
  Selector({
    Key key,
    @required ValueWidgetBuilder<S> builder,
    @required S Function(BuildContext, A) selector,
    ShouldRebuild<S> shouldRebuild,
    Widget child,
  })  : assert(selector != null),
        super(
          key: key,
          shouldRebuild: shouldRebuild,
          builder: builder,
          selector: (context) => selector(context, Provider.of(context)),
          child: child,
        );
}

可以看出 Selector 继承自 Selector0,追踪 Selector0 源码,它通过 buildWithChild 创建 Widget

代码语言:javascript
复制
class _Selector0State<T> extends SingleChildState<Selector0<T>> {
  T value;
  Widget cache;
  Widget oldWidget;
  @override
  Widget buildWithChild(BuildContext context, Widget child) {
    final selected = widget.selector(context);
    var shouldInvalidateCache = oldWidget != widget ||
        (widget._shouldRebuild != null && widget._shouldRebuild.call(value, selected)) ||
        (widget._shouldRebuild == null && !const DeepCollectionEquality().equals(value, selected));
    if (shouldInvalidateCache) {
      value = selected;
      oldWidget = widget;
      cache = widget.builder(
        context,
        selected,
        child,
      );
    }
    return cache;
  }
}

这里的 A,S,可以看出 A 是 selector 函数的入参,S 是函数的返回值,这里将 A 通过 Provider.of(context) 转换成了 Provider。对比上述 Selector 的例子,这里的 A 对应 Model1,S 对应 count。这里还有一个 shouldRebuild,看看函数的定义:

代码语言:javascript
复制
typedef ShouldRebuild<T> = bool Function(T previous, T next);

通过比较前一个值和当前值,决定是否重新 rebuild 页面,如果返回 true,则页面会被重新渲染一次,返回 false,页面不会被重新渲染。具体判断逻辑可以参考 _Selector0State 的 buildWithChild 方法。

可以看出,相对于 Consumer,Selector 缩小了数据监听的范围,并且可以根据自身的业务逻辑自定义是否刷新页面,从而避免了很多不必要的页面刷新,从而提高了性能。

在 Provider SDK 代码结构的 selector.dart 文件里,可以看出还定义了 Selector2、Selector3、Selector4、Selector5、Selector6。这里以 Selector2 做示例讲解其用法:

代码语言:javascript
复制
class Selector2<A, B, S> extends Selector0<S> {
  Selector2({
    Key key,
    @required ValueWidgetBuilder<S> builder,
    @required S Function(BuildContext, A, B) selector,
    ShouldRebuild<S> shouldRebuild,
    Widget child,
  })  : assert(selector != null),
        super(
          key: key,
          shouldRebuild: shouldRebuild,
          builder: builder,
          selector: (context) => selector(
            context,
            Provider.of(context),
            Provider.of(context),
          ),
          child: child,
        );
}

可以看出,Selector2 同样也是继承了 Selector0,不同的是 selector 函数有了两个入参 A 和 B,S 是函数的返回值。也就是说可以通过 Selector2 监听 2 个 Provider,可以从这 2 个 Provider 中自定义 S 的值变化监听。其他 Selector 只是监听的 Provider 更多罢了。 如果大于 6 个 Provider 需要监听,就需要自定义 Selector 方法了。 示例中我们用 Selector2 同时监听 Model1 和 Model2 的变化,对两个 Model 中的 count 进行加和计算。

代码语言:javascript
复制
Selector2<Model1, Model2, int>(
  selector: (context, model1, model2) {
    return model1.count + model2.count;
  },
  builder: (context, totalCount, child) {
    return Text(
      'Model1和Model2 count合计:$totalCount');
  }

Selector3, Selector4…,还有 Consumer2,Consumer3…,这里就不做赘述了。

Consumer 和 Selector 性能验证

经过上面的示例,我们已经对 Consumer 和 Selector 有所了解。Consumer 可以避免 widget 多余的 rebuild,当 Consumer 中监听的 value 不发生变化,其包裹的 widget 不会 Rebuild。 Selector 在 Consumer 基础上提供了更加精确的监听,还支持自定义 rebuild,可以更加灵活地控制 widget rebuild 问题。

下面我们一起来验证下 Consumer 和 Selector rebuild 的情况。Step3 图示中,我们定义了两个按钮,一个用于累加 Model1 中的 count,一个用于累加 Model2 中的 count;同时演示了 Selector2 和 Consumer 的用法;定义了 Widget1,Widget2,带 Selector 的 Widget4,用于验证 rebuild 情况。Model1的 count 值用绿色标识,Model2 的 count 值用红色标识。

本篇文章示例源码:https://github.com/xiaomanzijia/FlutterProvider

Widget1, 在 build 方法中打印 “Widget1 build”。

代码语言:javascript
复制
class Widget1 extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => StateWidget1();
}
class StateWidget1 extends State<Widget1> {
  @override
  Widget build(BuildContext context) {
    print('Widget1 build');
    return Text('Widget1', style: Theme.of(context).textTheme.subhead);
  }
}

Widget2, 在 build 方法中打印 "Widget2 build"。 Widget3, 监听了 Model2 count 变化,在 builder 方法中打印 “Widget3 build”。

Widget4,在 build 方法中打印 "Widget4 build",build 方法返回一个 Selector,在 Selector 的 builder 方法中打印 “Widget4 Selector build”,Selector 监听 Model1 count 变化。

代码语言:javascript
复制
class Widget4 extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => StateWidget4();
}
class StateWidget4 extends State<Widget4> {
  @override
  Widget build(BuildContext context) {
    print('Widget4 build');
    return Selector<Model1, int>(
      builder: (context, count, child) {
        print('Widget4 Selector build');
        return Text('Widget4 Model1 count:$count', style: Theme.of(context).textTheme.subhead.copyWith(color: Colors.green),);
      },
      selector: (context, model) => model.count,
    );
  }
}

所有条件具备,我们运行 StatsView 页面,日志打印如下:

代码语言:javascript
复制
Selector2 build
Model1 Consumer build
Model2 Consumer build
Widget1 build
Widget2 build
Widget3 build
Widget4 build
Widget4 Selector build

可以看出,widget 渲染顺序是根据页面元素布局顺序从上到下开始渲染。 点击 Model1 count++ 按钮,可以看到所有绿色标识的地方,count 已更新。日志打印如下:

代码语言:javascript
复制
Selector2 build
Model1 Consumer build
Model2 Consumer build
Widget1 build
Widget2 build
Widget3 build
Widget4 build
Widget4 Selector build

啥!!!怎么和第一次加载页面日志一样,更新 Model1 的 count,不应该只 build 监听 Model1 相关的 widget 吗?我们改下代码,把 Widget4 作为全局变量,在 initState 的时候初始化。

代码语言:javascript
复制
class StateStatsView extends State<StatsView> {
  final ValueNotifier<int> _counter = ValueNotifier<int>(0);
  var widget4;
  @override
  void initState() {
    widget4 = Widget4();
    super.initState();
  }
  @override
  Widget build(BuildContext context) {
    _counter.value = Provider.of<Model1>(context).count;
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          ..., //此处省略部分实例源码
          Widget1(),
          Widget2(),
          Widget3(),
          widget4,
        ],
      ),
    );
  }
}

运行后再次点击 Model1 count++ 按钮,日志打印如下:

代码语言:javascript
复制
Selector2 build
Model1 Consumer build
Model2 Consumer build
Widget1 build
Widget2 build
Widget3 build
Widget4 Selector build

可以看到,“Widget4 build” 的日志不会再打印,但是 “Widget4 Selector build” 的日志仍在打印。我们再改下代码,把 Widget4 中的 selector 作为全局变量,在 initState 的时候初始化。

代码语言:javascript
复制
class Widget4 extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => StateWidget4();
}
class StateWidget4 extends State<Widget4> {
  Selector<Model1, int> selector;
  @override
  void initState() {
    selector = buildSelector();
    super.initState();
  }
  @override
  Widget build(BuildContext context) {
    print('Widget4 build');
    return selector;
  }
  Selector<Model1, int> buildSelector() {
    return Selector<Model1, int>(
    builder: (context, count, child) {
      print('Widget4 Selector build');
      return Text('Widget4 Model1 count:$count', style: Theme.of(context).textTheme.subhead.copyWith(color: Colors.green),);
    },
    selector: (context, model) => model.count,
  );
  }
}

运行后再次点击 Model1 count++ 按钮,日志打印如下:

代码语言:javascript
复制
Selector2 build
Model1 Consumer build
Model2 Consumer build
Widget1 build
Widget2 build
Widget3 build

可以看到,“Widget4 Selector build” 的日志没有打印了,Widget4 中监听的 Model1 中的 count 也正常更新了。 通过前面 3 步的验证,我们可以得知当 ChangeNotifier(这里即 Model1)通知更新(notifyListener)时,在 Model1 作用域下的 Widget 都会触发 build,Selector,Consumer 实质也就是一个 Widget,当我们的数据需要 Selector 或 Consumer 包裹时,建议在 initState 的时候先把 widget 创建好,可以避免不必要的 build。

其他

1.listen

如果我们把 “Model1 count++” 按钮点击事件代码改下

代码语言:javascript
复制
FlatButton(
  color: Colors.blue,
  child: Text('Model1 count++'),
  onPressed: () {
    Provider.of<Model1>(context).count++;
  },
),

区别在于少了 listen:false,点击按钮你会看到下面的错误日志:

代码语言:javascript
复制
Tried to listen to a value exposed with provider, from outside of the widget tree.
This is likely caused by an event handler (like a button's onPressed) that called
Provider.of without passing `listen: false`.
To fix, write:
Provider.of<Model1>(context, listen: false);
It is unsupported because may pointlessly rebuild the widget associated to the
event handler, when the widget tree doesn't care about the value.

官方注释页对 listen 做了说明,listen=true,意味着被监听的 ChangeNotifier 中的值发生变化,对应的 widget 就会被 rebuild,listen=false,则不会被 rebuild。在 widget 树外调用 Provider.of 方法,必须加上 listen:false。

2.扩展

Provider 从 4.1.0 版本开始支持了扩展方法,当前示例基于 4.0.5+1 讲解,这里暂不做赘述,具体可看 Changelog (https://pub.flutter-io.cn/packages/provider/changelog)。

before

after

Provider.of(context, listen: false)

context.read()

Provider.of(context)

context.watch

其他状态管理组件

组件

介绍

Provider (https://pub.dev/packages/provider)

官方推荐,基于 InheritedWidget 实现

ScopedModel (https://pub.dev/packages/scoped_model)

基于 InheritedWidget 实现,和 Provider 原理和写法都很类似

BLoC (https://pub.dev/packages/flutter_bloc)

基于 Stream 实现的 ,该模式需要对响应式编程(比如 RxDart,RxJava)有一定的理解。核心概念:输入事件 Sink<Event> input,输出事件 Stream<Data> output

Redux (https://pub.flutter-io.cn/packages/flutter_redux)

Web 开发中 React 生态链中 Redux 包的 Flutter 实现,在前端比较流行,一种单向数据流架构。核心概念:存储对象 Store、事件操作 Action、处理和分发事件 Reducer、组件刷新 View

Mobx (https://pub.dev/packages/flutter_mobx)

本来是一个 JavaScript 的状态管理库,后迁移到 dart 版本。核心概念:Observables、Actions、Reactions

这里不对其他组件做赘述,读者有兴趣可以研究一下,了解其他组件的实现原理。

总结

本篇文章主要介绍了官方推荐使用的 Provider 组件,结合源码和平时业务开发过程中遇到的问题,介绍了几种常用的使用方式,希望大家能熟练使用,在业务场景中能灵活运用。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-01-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 政采云技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 用法
    • Step1:添加依赖
      • Step2:观察结构
        • Step3:示例简介
          • Step4:创建一个 ChangeNotifier
            • Step5:创建 ChangeNotifierProvider
              • 方式一:通过 ChangeNotifierProvider
              • 方式二:通过 ChangeNotifierProvider.value
            • Step6:在页面中监听状态变更,其他使用方式
              • ValueNotifier
              • MultiProvider
              • ProxyProvider
              • FutureProvider
              • StreamProvider
              • Consumer
              • Selector
              • Consumer 和 Selector 性能验证
            • 其他
              • 1.listen
              • 2.扩展
            • 其他状态管理组件
            • 总结
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档