Flutter使用RepositoryProvider解决跨组件传值问题

目录
  • 前言
  • RepositoryProvider定义
  • RepositoryProvider 应用
  • 总结

前言

在实际开发过程中,经常会遇到父子组件传值的情况,通常来说会有三种方式:

  • 构造函数传值:父组件将子组件需要的对象通过构造函数传递给子组件;
  • 单例对象:构建单例对象,使得父子组件使用的是同一个对象;
  • 容器:将对象存入容器中,父子组件使用的时候直接从容器中获取。

第一种方式的缺陷是如果组件嵌套很深,传递数据对象需要层层传递,将导致代码很难维护。第二种方式需要自己构建单例类,而实际上要传递的对象可能存在很多个实例。第三种和单例类似,如果往容器存储不定数量的实例对象是不合适的。flutter_bloc 提供了一种基于组件的依赖注入方式解决这类问题,通过使用 RepositoryProvider,可以为组件树的子组件提供共享对象,这个共享对象只限在组件树中使用,可以通过 Provider 的方式访问该对象。

RepositoryProvider定义

Repository 实际上是 Provider 的一个子类,通过注册单例的方式实现组件树对象共享,因此其注册的对象会随着 Provider 的注销而销毁,而且这个对象无需是 Bloc 子类。因此在无法使用 Bloc 传输共享对象的时候,可以使用 RepositoryProvider 来完成。RepositoryProvider有两种方式创建对象共享,create 和 value 方式,其中 create 是通过调用一个方法创建新的对象,而 value 是共享一个已有的对象。RepositoryProvider的定义如下:

class RepositoryProvider<T> extends Provider<T>
    with RepositoryProviderSingleChildWidget {
  RepositoryProvider({
    Key? key,
    required Create<T> create,
    Widget? child,
    bool? lazy,
  }) : super(
          key: key,
          create: create,
          dispose: (_, __) {},
          child: child,
          lazy: lazy,
        );

  RepositoryProvider.value({
    Key? key,
    required T value,
    Widget? child,
  }) : super.value(
          key: key,
          value: value,
          child: child,
        );
  
  static T of<T>(BuildContext context, {bool listen = false}) {
    try {
      return Provider.of<T>(context, listen: listen);
    } on ProviderNotFoundException catch (e) {
      if (e.valueType != T) rethrow;
      throw FlutterError(
        '''
        RepositoryProvider.of() called with a context that does not contain a repository of type $T.
        No ancestor could be found starting from the context that was passed to RepositoryProvider.of<$T>().

        This can happen if the context you used comes from a widget above the RepositoryProvider.

        The context used was: $context
        ''',
      );
    }
  }
}

RepositoryProviderSingleChildWidget本身是一个空的 Mixin:

mixin RepositoryProviderSingleChildWidget on SingleChildWidget {}

,注释上写着其用途是为了方便 MultiRepositoryProvider推断RepositoryProvider的类型设计。可以看到实际上 RepositoryProvider就是 Provider,只是将静态方法 of 的listen 参数默认设置为 false 了,也就是不监听状态对象的变化。我们在子组件中通过两种方式访问共享对象:

// 方式1
context.read<T>()
// 方式2
RepositoryProvider.of<T>(context)

如果有多个对象需要共享,可以使用MultiRepositoryProvider,使用方式也和 MultiProvider 相同 :

MultiRepositoryProvider(
  providers: [
    RepositoryProvider<RepositoryA>(
      create: (context) => RepositoryA(),
    ),
    RepositoryProvider<RepositoryB>(
      create: (context) => RepositoryB(),
    ),
    RepositoryProvider<RepositoryC>(
      create: (context) => RepositoryC(),
    ),
  ],
  child: ChildA(),
)

RepositoryProvider 应用

回顾一下我们之前使用 BlocBuilder 仿掘金个人主页的代码,在里面我们页面分成了三个部分:

  • 头像及背景图:_getBannerWithAvatar
  • 个人资料:_getPersonalProfile
  • 个人数据统计:_getPersonalStatistic

分别使用了三个构建组件的函数完成。对应的界面如下所示:

PersonalEntity personalProfile = personalResponse.personalProfile!;
        return Stack(
          children: [
            CustomScrollView(
              slivers: [
                _getBannerWithAvatar(context, personalProfile),
                _getPersonalProfile(personalProfile),
                _getPersonalStatistic(personalProfile),
              ],
            ),
            // ...
          ],
        );
      },
//...

可以看到,每个函数都需要把 personalProfile 这个对象通过函数的参数传递,而如果函数中的组件还有下级组件需要这个对象,还需要继续往下传递。这要是需要修改对象传值的方式,需要沿着组件树逐级修改,维护起来会很不方便。我们改造一下,将三个函数构建组件分别换成自定义的 Widget,并且将个人统计区换成两级组件,改造后的组件树如下所示(省略了装饰类的层级)。

组件层级

拆解完之后,我们就可以简化personalProfile 的传值了。

RepositoryProvider.value(
  child: CustomScrollView(
    slivers: [
      const BannerWithAvatar(),
      const PersonalProfile(),
      const PersonalStatistic(),
    ],
  ),
  value: personalProfile,
),
// ...

这里使用value模式是因为 personalProfile 已经被创建了。然后在需要使用 personalProfile 的地方,使用context.read<PersonalEntity>()就可以从 RepositoryProvider 中取出personalProfile对象了,从而使得各个子组件无需再传递该对象。以BannerWithAvatar 为例,如下所示:

class BannerWithAvatar extends StatelessWidget {
  final double bannerHeight = 230;
  final double imageHeight = 180;
  final double avatarRadius = 45;
  final double avatarBorderSize = 4;

  const BannerWithAvatar({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SliverToBoxAdapter(
      child: Container(
        height: bannerHeight,
        color: Colors.white70,
        alignment: Alignment.topLeft,
        child: Stack(
          children: [
            Container(
              height: bannerHeight,
            ),
            Positioned(
              top: 0,
              left: 0,
              child: CachedNetworkImage(
                imageUrl:
                    'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=688497718,308119011&fm=26&gp=0.jpg',
                height: imageHeight,
                width: MediaQuery.of(context).size.width,
                fit: BoxFit.fill,
              ),
            ),
            Positioned(
              left: 20,
              top: imageHeight - avatarRadius - avatarBorderSize,
              child: _getAvatar(
                context.read<PersonalEntity>().avatar,
                avatarRadius * 2,
                avatarBorderSize,
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _getAvatar(String avatarUrl, double size, double borderSize) {
    return Stack(alignment: Alignment.center, children: [
      Container(
        width: size + borderSize * 2,
        height: size + borderSize * 2,
        clipBehavior: Clip.antiAlias,
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(size / 2 + borderSize),
        ),
      ),
      Container(
        width: size,
        height: size,
        clipBehavior: Clip.antiAlias,
        decoration: BoxDecoration(
          color: Colors.black,
          borderRadius: BorderRadius.circular(size / 2),
        ),
        child: CachedNetworkImage(
          imageUrl: avatarUrl,
          height: size,
          width: size,
          fit: BoxFit.fill,
        ),
      ),
    ]);
  }
}

可以看到整个代码更简洁也更易于维护了。

总结

本篇介绍了 RepositoryProvider 的使用,实际上 RepositoryProvider 借用Provider 实现了一个组件树上的局部共享对象容器。通过这个容器,为RepositoryProvider的子组件树注入了共享对象,使得子组件可以从 context 中或使用RepositoryProvider.of 静态方法获取共享对象。通过这种方式避免了组件树的层层传值,使得代码更为简洁和易于维护。

以上就是Flutter使用RepositoryProvider解决跨组件传值问题的详细内容,更多关于Flutter跨组件传值的资料请关注我们其它相关文章!

(0)

相关推荐

  • Flutter页面传值的几种方式

    今天来聊聊Flutter页面传值的几种方式: InheritWidget Notification Eventbus (当前Flutter版本:2.0.4) InheritWidget 如果看过Provider的源码的同学都知道,Provider跨组件传值的原理就是根据系统提供的InheritWidget实现的,让我们来看一下这个组件. InheritWidget是一个抽象类,我们写一个保存用户信息的类UserInfoInheritWidget继承于InheritWidget: class Us

  • Flutter使用AnimatedBuilder实现动效复用

    目录 前言 AnimatedBuilder 介绍 Transform 组件介绍 应用 总结 前言 我们之前讲述了动画构建的两种方式,Animation 和 AnimationWidget,这两种构建动画都是将组件和动画一起完成的.有些时候,我们只是想动效复用,而不关心组件构建,这个时候就可以使用 AnimatedBuilder 了. AnimatedBuilder 介绍 根据官方文档说明,AnimatedBuilder 的使用要点如下: An AnimatedBuilder understand

  • Android Flutter实现五种酷炫文字动画效果详解

    目录 前言 波浪涌动效果 波浪线跳动文字组 彩虹动效 滚动广告牌效果 打字效果 其他效果 自定义效果 总结 前言 偶然逛国外博客,看到了一个介绍文字动画的库,进入 pub 一看,立马就爱上这个动画库了,几乎你能想到的文字动画效果它都有!现在正式给大家安利一下这个库:animated_text_kit.本篇我们介绍几个酷炫的效果,其他的效果大家可以自行查看官网文档使用. 波浪涌动效果 波浪涌动 上面的动画效果只需要下面几行代码,其中loadUntil用于控制波浪最终停留的高度,取值是0-1.0,如

  • Flutter使用AnimatedSwitcher实现场景切换动画

    目录 前言 AnimatedSwitcher 介绍 应用 总结 前言 在应用中,我们经常会遇到切换组件的场景,比如点击一个按钮后,将当前的图片为另一张图片:或者是翻转卡片,显示卡片详情.在 Flutter 中提供了 AnimatedSwitcher 这个动画组件来实现页面内的场景切换. AnimatedSwitcher.gif AnimatedSwitcher 介绍 AnimatedSwitcher 通过动效完成其子组件的切换,默认的效果是 FadeTransition.当其子组件发生改变的时候

  • Flutter使用AnimatedOpacity实现图片渐现动画

    目录 前言 AnimatedOpacity 简介 AnimatedOpacity 应用 图片渐现过渡 总结 前言 我们介绍了几篇 Flutter 的动画控制类,相信大家对动画也有了一定的了解,可以通过这些基础的动画控制类实现自己想要的动画效果.在 Flutter 中也提供了一些封装好的动画组件,以便我们快速应用.本篇我们来介绍渐现效果 —— AnimatedOpacity. AnimatedOpacity 简介 顾名思义,AnimatedOpacity 就是用于动态展示组件的透明度.实际上,它实

  • Flutter使用RepositoryProvider解决跨组件传值问题

    目录 前言 RepositoryProvider定义 RepositoryProvider 应用 总结 前言 在实际开发过程中,经常会遇到父子组件传值的情况,通常来说会有三种方式: 构造函数传值:父组件将子组件需要的对象通过构造函数传递给子组件: 单例对象:构建单例对象,使得父子组件使用的是同一个对象: 容器:将对象存入容器中,父子组件使用的时候直接从容器中获取. 第一种方式的缺陷是如果组件嵌套很深,传递数据对象需要层层传递,将导致代码很难维护.第二种方式需要自己构建单例类,而实际上要传递的对象

  • vue 解决兄弟组件、跨组件深层次的通信操作

    兄弟组件之间的通信同样是在项目中经常会遇到的组件间的通信问题之一, 这种问题的最根本方法就是: 把兄弟组件内部的变量提升到一个中央仓库. 借助父级组件链式交互 使子组件1 通过 $emit 通知父级, 父级再通过响应 子组件1 的事件去触发子组件2的事件,这样的链式操作,在子组件不多的时候,但是一个不错的解决方法 子组件1 <template> <div> <p @click="$emit('fromFirst','来自A组件')">first组件&

  • Vue如何解决兄弟组件之间传值问题

    目录 解决兄弟组件之间传值问题 bus可以通过两种方式来实现 各类组件间传值方法(父子.兄弟.页级) 父子关系组件 兄弟关系组件 页级关系组件 解决兄弟组件之间传值问题 vue中 父组件向子组件传递参数时,可以通过prop来传递参数,prop可以是数组形式,也可以是对象格式,子向父组件传递参数时,通过$emit来传递,$emit('方法名', 传参),来实现子组件和父组件数据交互,但是在父组件中,有好多个子组件,这些子组件怎么样来进行数据通讯呢? 有的同学会立马想到 vuex ,答案是:可以的,

  • 解决vue组件props传值对象获取不到的问题

    先说问题,父组件利用props向子组件传值,浏览器 console 有这个值,但是获取不到内部的属性,困了我3个小时,真的** personal console 以下为原代码 1.home.vue(父组件)--personal是被传的参数 <!--子组件--> <form-picker class="form-picker" :personal="personal" > </form-picker> export default

  • vue中父子组件传值,解决钩子函数mounted只运行一次的操作

    因为mounted函数只会在html和模板渲染之后会加载一次,但是在子组件中只有第一次的数据显示是正常的,所以需要再增加一个updated函数,在更新之后就可以重新进行取值加载,完成数据的正常显示. beforCreate(创建之前) Created(创建之后) beforMount(载入之前) Mounted(载入之后) beforUpdate(更新之前) Updated(更新之后) beforDestroy(销毁之前) Destroyed(销毁之后) activate(keep-alive组

  • vue组件传值(高级)、属性传值、反向传值、跨级传值实例详解

    目录 一.属性传值——父传子 二.反向传值——子传父$emit 三.反向传值——子传父--sync 四.反向传值——子传父--v-model v-model指令的修饰符: 五.多层(跨级)组件传值 六.$ parent/$root.$children/$refs 七.Vue 依赖注入 - Provide/Inject(重点) 八.中央事件总线bus 一.属性传值——父传子 父组件通过属性传值给子组件 父组件修改数据后会刷新页面并重新传值给子组件 子组件可以修改父组件传的值并刷新自己的页面 但是并

  • vue2.0中组件传值的几种方式总结

    目录 搭建好测试环境 1.方法一 父传子 子传父 2.方法二 父传子 子传父 奇怪的传值 3.方法三 4.兄弟组件之间传值 总结 搭建好测试环境 app.vue <template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App&quo

  • js前端解决跨域问题的8种方案(最新最全)

    1.同源策略如下: URL 说明 是否允许通信 http://www.a.com/a.js http://www.a.com/b.js 同一域名下 允许 http://www.a.com/lab/a.js http://www.a.com/script/b.js 同一域名下不同文件夹 允许 http://www.a.com:8000/a.js http://www.a.com/b.js 同一域名,不同端口 不允许 http://www.a.com/a.js https://www.a.com/b

  • vue解决跨域路由冲突问题思路解析

    vue 简介 Vue.js(读音 /vjuː/, 类似于 view) 是一套构建用户界面的渐进式框架. Vue 只关注视图层, 采用自底向上增量开发的设计. Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件. Vue 学习起来非常简单,本教程基于 Vue 2.1.8 版本测试. 当我们在路由里面配置成以下代理可以解决跨域问题 proxyTable: { '/goods/*': { target: 'http://localhost:3000' }, '/users/*

  • vue.js使用代理和使用Nginx来解决跨域的问题

    使用Nginx 反向代理解决跨域问题(vue.js使用代理去掉vue.js因为跨域而触发的options请求) 我们的项目还是需要node.js作为容器的 一.Windows 下安装Nginx (官网下载稳定版http://nginx.org/en/download.html) 二.修改config里的nginx.conf文件的server server { listen 8899;// 你的端口 server_name localhost; root C:/ZOBSF_F/dist;//你打包

随机推荐