header-bg.jpg
Provider 在实际项目中的应用
发表于 2020-03-16 23:50
|
分类于 学海无涯
|
评论次数 0
|
阅读次数 49

flutter.png

StatefulWidget 是 Flutter 默认的状态管理方式,它只适合一些功能单一的小组件的内部状态管理,一旦组件中状态多了起来,页面稍微复杂一点,setState 就是高射炮打蚊子,只想更改页面中很小的一部分,整个页面却都被重新 build 了,随之带来的就是页面卡顿。而用 Provider 就可以解决这种问题,有了它,大多数情况下我们都可以不使用 StatefulWidget,所以本文将记录 Provider 在我实际项目中的应用。

使用 ChangeNotifierProvider.value 管理状态

一开始看了官方文档和一些资料,我发现 Provider 类型是真的花里胡哨,我人直接给看晕了,有 ProviderListenableProviderStreamProvider 等等,但其实我发现在我的项目中(一个视频 App),只需要使用 ChangeNotifierProvider 就可以满足日常开发中遇到的各种需求,下面先讲讲如何使用。

创建模型类

模型类,无论是前端还是后端,都有这种类似的概念,例如在 Vue 中,数据会放在 VueX 中进行管理,在 Laravel 中,数据会放在 Eloquent 中进行管理,在 Provider 中,我们同样需要创建一个模型类来管理数据。

首先通过混入 ChangeNotifier 抽象类,让模型类具有管理收听者的能力:

class HomeModel with ChangeNotifier {
  var opacity = .5;
  
  void setOpacity(double value) {
    opacity = value;
    notifyListeners();
  }
}

ChangeNotifier 继承自 Listenable 类,实现了addListenerremoveListener 等操作,这是一个典型的观察者模式,它可以自动管理收听者及通知收听者。

上面的代码中,模型类 HomeModel 对外暴露了一个数据 opacity,我们在构建 Widget 时可以使用该数据,并且可以调用 setOpacity 方法更新该数据,并由 notifyListeners 通知 Widget 刷新状态。

创建 Provider

首先创建一个 HomeModel 的实例 model,然后将 model 传递给 ChangeNotifierProvider.value,完成 Provider 的创建:

final model = HomeModel();


Widget build(BuildContext context) {
  return Scaffold(
    body: SafeArea(
      child: ChangeNotifierProvider.value(
        value: model,
        child: Opacity(
          opacity: model.opacity,
          child: Container(color: Colors.blue)
        ),
      ),
    ),
  );
}

上面的代码中 Opacity 组件使用了 HomeModel 中的 opacity 值,最终的构建效果是一个半透明的蓝色容器。

使用 Selector 更新数据并刷新 UI

ConsumerSelector 组件都可以与模型类建立依赖关系,当我们在模型类中调用 notifyListeners 时,便会通知到 ConsumerSelector 进行更新。

MyChangeNotifier variable;

ChangeNotifierProvider.value(
  value: variable,
  child: ...
)

不过它们之间当然是有区别的,Selector 组件可以只依赖模型中的具体某个数据,仅当依赖的那个数据发生变化时才会重新构建,而 Consumer 则是一把梭,整个模型类只要有任何数据发生改变,就会刷新子组件,这就有点 setState 内味儿了,使用 Provider 本来就是为了优化性能的,减少构建范围的,我在项目中没有用过该组件。

使用 ChangeNotifierProvider 销毁状态

ChangeNotifierProvider 除了 .value 命名构造函数以外,还可以使用默认的构造函数进行创建,例如:

final model = HomeModel();


Widget build(BuildContext context) {
  return Scaffold(
    body: SafeArea(
      child: ChangeNotifierProvider(
        create: (_) => model,
        child: Opacity(
          opacity: model.opacity,
          child: Container(color: Colors.blue)
        ),
      ),
    ),
  );
}

默认构造函数和 .value 命名构造函数有什么区别?

经过我的实践,我发现 .value 创建的 Provider 不会在页面销毁时调用模型类的 dispose 方法,而使用默认的构造函数创建的 Provider则会在页面销毁时自动调用模型类的 dispose 方法,例如:

import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';

class VideoModel extends ChangeNotifier {
  VideoModel() {
    _initController();
  }

  var duration = const Duration(milliseconds: 1);
  VideoPlayerController controller;
  void _handleVideoChange() {
    // 播放器状态改变时的回调处理
  }

  Future<void> _initController() async {
    videoFile = await pickedVideo.file;
    controller = VideoPlayerController.file(videoFile)
    ..initialize().then((e) {
      duration = controller.value.duration;
      notifyListeners();
    })
    ..addListener(_handleVideoChange);
  }

  // 页面销毁
  
  void dispose() {
    controller.dispose();
    controller.removeListener(_handleVideoChange);
    super.dispose();
  }
}

上面的 VideoModel 模型类,在初始化时创建了 VideoController,并添加了 controller 的侦听者,当退出该页面时,我需要销毁 controller 并释放资源,默认的构造函数就非常适合干这个:

class VideoPage extends StatelessWidget {
  final model = VideoModel();

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: ChangeNotifierProvider<VideoModel>(
          create: (_) => model,
          child: body,
        ),
      ),
    );
  }
}

模型类的 dispose 方法还是挺有用的,由于我使用 Provider,大多数页面不会使用 StatefulWidget,而 StatelessWidget 不带有 dispose 钩子,这种情况就非常适合使用默认的构造函数来创建 Provider,而非 .value 构造函数。

发布评论
还没有评论,快来抢沙发吧!