当前位置: 首页 > news >正文

Flutter之riverpod状态管理Widget UI详解

一、riverpod状态管理中所涉及到的widget UI组件对比分析

UI 组件状态类型语法形式特点
ConsumerWidget有状态无状态形式

最常用,通过WidgetRef访问provider,

所谓无状态,是指ConsumerWidegt不像StatefulWidegt那样创建state,在它内部不可以定义状态变量,然后再调用setState()更新状态和UI,类似于statelessWidget,但是可以在它内部引用外部的或全局状态提供者provider,以达到全局状态提供者状态更新时,ConsumerWidget也重新构建UI

ConsumerStatefulWidget有状态有状态形式

具有完整生命周期,可管理内部状态,

类似于StatefulWidget,

创建状态,重载createState()

初始化状态,重截initState(),

状态销毁,重载dispose()

Consumer有状态 ---局部UI重建,只重建部分UI,优化性能
ProviderScope有状态 ---创建新的provider作用域,可覆盖父级provider
HookWidget有状态无状态形式使用 Hooks(钩子),依赖flutter_hooks这个库,使用useState在无状态Widget中管理状态和其他副作用,生命周期使用useEffect
HookConsumerWidget有状态无状态形式可以同时使用 Hooks + Riverpod管理状态,生命周期使用useEffect

下面用代码分析比较一下使用场景:

1. ConsumerWidget - 最常用的UI组件

final counterProvider = StateProvider<int>((ref) => 0);class ConsumerWidgetExample extends ConsumerWidget {const ConsumerWidgetExample({super.key});@overrideWidget build(BuildContext context, WidgetRef ref) {final counter = ref.watch(counterProvider);return Card(child: Padding(padding: const EdgeInsets.all(16.0),child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: [const Text('1. ConsumerWidget',style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),),const SizedBox(height: 10),Text('这是一个无状态组件,通过WidgetRef访问provider'),const SizedBox(height: 10),Text('计数器: $counter', style: const TextStyle(fontSize: 20)),const SizedBox(height: 10),ElevatedButton(onPressed: () => ref.read(counterProvider.notifier).state++,child: const Text('增加计数'),),],),),);}
}

2. ConsumerStatefulWidget - 需要内部状态的UI组件

final counterProvider = StateProvider<int>((ref) => 0);class ConsumerStatefulWidgetExample extends ConsumerStatefulWidget {const ConsumerStatefulWidgetExample({super.key});@overrideConsumerState<ConsumerStatefulWidgetExample> createState() => _ConsumerStatefulWidgetExampleState();
}class _ConsumerStatefulWidgetExampleState extends ConsumerState<ConsumerStatefulWidgetExample> {int _localClicks = 0;@overridevoid initState() {super.initState();debugPrint('ConsumerStatefulWidget 初始化');}@overridevoid dispose() {debugPrint('ConsumerStatefulWidget 被销毁');super.dispose();}@overrideWidget build(BuildContext context) {final counter = ref.watch(counterProvider);return Card(child: Padding(padding: const EdgeInsets.all(16.0),child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: [const Text('2. ConsumerStatefulWidget',style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),),const SizedBox(height: 10),Text('这是一个有状态组件,可以管理内部状态'),const SizedBox(height: 10),Text('全局计数器: $counter', style: const TextStyle(fontSize: 16)),Text('本地点击次数: $_localClicks', style: const TextStyle(fontSize: 16)),const SizedBox(height: 10),Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,children: [ElevatedButton(onPressed: () {ref.read(counterProvider.notifier).state++;},child: const Text('全局+1'),),ElevatedButton(onPressed: () {setState(() {_localClicks++;});},child: const Text('本地+1'),),],),],),),);}
}

3. Consumer - 用于局部UI重建

final counterProvider = StateProvider<int>((ref) => 0);class ConsumerExample extends ConsumerWidget {const ConsumerExample({super.key});@overrideWidget build(BuildContext context, WidgetRef ref) {return Card(child: Padding(padding: const EdgeInsets.all(16.0),child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: [const Text('3. Consumer',style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),),const SizedBox(height: 10),const Text('使用Consumer只重建UI的特定部分:'),const SizedBox(height: 10),// 这个Text不会在计数器变化时重建const Text('这是静态文本,不会重建'),const SizedBox(height: 10),// 只有Consumer内的部分会在计数器变化时重建Consumer(builder: (context, ref, child) {final counter = ref.watch(counterProvider);return Text('动态计数: $counter',style: const TextStyle(fontSize: 20, color: Colors.blue),);},),const SizedBox(height: 10),ElevatedButton(onPressed: () => ref.read(counterProvider.notifier).state++,child: const Text('增加计数'),),],),),);}
}

4. ProviderScope - 用于创建新的provider作用域

    ProviderScope示例1

final counterProvider = StateProvider<int>((ref) => 0);class ProviderScopeExample extends StatelessWidget {const ProviderScopeExample({super.key});@overrideWidget build(BuildContext context) {// 创建一个新的provider作用域,可以覆盖父级的providerreturn ProviderScope(child: Card(child: Padding(padding: const EdgeInsets.all(16.0),child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: [const Text('4. ProviderScope',style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),),const SizedBox(height: 10),const Text('创建一个新的provider作用域'),const SizedBox(height: 10),// 在这个作用域内,可以覆盖父级的providerConsumer(builder: (context, ref, child) {final counter = ref.watch(counterProvider);return Text('计数器: $counter');},),],),),),);}
}

ProviderScope示例2:当我们有一个ListView显示产品列表,每个项目都需要知道正确的产品ID或索引时:

class ProductItem extends StatelessWidget {const ProductItem({super.key, required this.index});final int index;@overrideWidget build(BuildContext context) {// do something with the index}}class ProductList extends StatelessWidget {@overrideWidget build(BuildContext context) {return ListView.builder(itemBuilder: (_, index) => ProductItem(index: index),);}}

在上面的代码中,我们将构建器的索引作为构造函数参数传递给 ProductItem 小部件,这种方法有效,但如果ListView重新构建,它的所有子项也将重新构建。作为替代方法,我们可以在嵌套的ProviderScope内部覆盖Provider的值:

// 1. Declare a Providerfinal currentProductIndex = Provider<int>((_) => throw UnimplementedError());class ProductList extends StatelessWidget {@overrideWidget build(BuildContext context) {return ListView.builder(itemBuilder: (context, index) {// 2. Add a parent ProviderScopereturn ProviderScope(overrides: [// 3. Add a dependency override on the indexcurrentProductIndex.overrideWithValue(index),],// 4. return a **const** ProductItem with no constructor argumentschild: const ProductItem(),);});}}class ProductItem extends ConsumerWidget {const ProductItem({super.key});@overrideWidget build(BuildContext context, WidgetRef ref) {// 5. Access the index via WidgetReffinal index = ref.watch(currentProductIndex);// do something with the index}}

在这种情况下:

  • 我们创建一个默认抛出UnimplementedErrorProvider
  • 通过将父ProviderScope添加到ProductItem小部件来覆盖其值。
  • 我们在ProductItembuild方法中监视索引。

这对性能更有益,因为我们可以将ProductItem作为const小部件创建在ListView.builder中。因此,即使ListView重新构建,除非其索引发生更改,否则我们的ProductItem将不会重新构建。

5. HookWidget - 利用hooks钩子在无状态下管理状态

假设你有一个计数器应用,你使用useState来管理计数值:

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';class HookWidgetExample extends HookWidget {const HookWidgetExample({super.key});@overrideWidget build(BuildContext context) {// 使用 useState Hook 来管理状态final counter = useState(0);// 使用 useEffect Hook 处理副作用useEffect(() {debugPrint('HookWidget 初始化或计数器变化: ${counter.value}');return () => debugPrint('HookWidget 清理效果');}, [counter.value]);// 使用 useMemoized 缓存计算结果final doubledValue = useMemoized(() {return counter.value * 2;}, [counter.value]);return Card(child: Padding(padding: const EdgeInsets.all(16.0),child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: [const Text('1. HookWidget',style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),),const SizedBox(height: 10),const Text('表面上是无状态组件,但实际上是有状态的'),const SizedBox(height: 10),Text('计数器: ${counter.value}'),Text('双倍值: $doubledValue'),const SizedBox(height: 10),ElevatedButton(onPressed: () => counter.value++,child: const Text('增加计数'),),],),),);}
}

6. HookConsumerWidget - 结合 Hooks 和 Riverpod

class HookConsumerWidgetExample extends HookConsumerWidget {const HookConsumerWidgetExample({super.key});@overrideWidget build(BuildContext context, WidgetRef ref) {// 使用 Hooks 管理本地状态final localCounter = useState(0);final animationController = useAnimationController(duration: const Duration(milliseconds: 500),);// 使用 Riverpod 管理全局状态final globalCounter = ref.watch(counterProvider);// 使用 useEffect 处理副作用useEffect(() {debugPrint('本地计数器变化: ${localCounter.value}');return null;}, [localCounter.value]);return Card(child: Padding(padding: const EdgeInsets.all(16.0),child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: [const Text('2. HookConsumerWidget',style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),),const SizedBox(height: 10),const Text('结合了 Hooks 和 Riverpod 的强大功能'),const SizedBox(height: 10),Text('本地计数器: ${localCounter.value}'),Text('全局计数器: $globalCounter'),const SizedBox(height: 10),Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,children: [ElevatedButton(onPressed: () => localCounter.value++,child: const Text('本地+1'),),ElevatedButton(onPressed: () => ref.read(counterProvider.notifier).state++,child: const Text('全局+1'),),],),],),),);}
}


文章转载自:

http://UYM8QbFE.hmbxd.cn
http://BgJRhSgr.hmbxd.cn
http://5OAufhNO.hmbxd.cn
http://Ag7FqerU.hmbxd.cn
http://P0ekIcoX.hmbxd.cn
http://mCLhNAct.hmbxd.cn
http://TOLYXPwZ.hmbxd.cn
http://HsRejADn.hmbxd.cn
http://v7sRKcBN.hmbxd.cn
http://vrKJfbZN.hmbxd.cn
http://kHvyorYX.hmbxd.cn
http://rvWSXo2O.hmbxd.cn
http://RQKBVxvc.hmbxd.cn
http://NVdIdUcZ.hmbxd.cn
http://v6tvHnRV.hmbxd.cn
http://bymSxb9Z.hmbxd.cn
http://WcTe1fQc.hmbxd.cn
http://fknojMD5.hmbxd.cn
http://yTp72DBV.hmbxd.cn
http://fFamYXX4.hmbxd.cn
http://Xpf9jJuk.hmbxd.cn
http://ecxTuVnQ.hmbxd.cn
http://T3muKCym.hmbxd.cn
http://rAuscQlJ.hmbxd.cn
http://i6AFV1aI.hmbxd.cn
http://7tBYrxZV.hmbxd.cn
http://q9tA6ASK.hmbxd.cn
http://Wu3HXP7J.hmbxd.cn
http://5W6gbjuB.hmbxd.cn
http://S5x85WFS.hmbxd.cn
http://www.dtcms.com/a/367514.html

相关文章:

  • 投标委托测试如何选择第三方检测机构?
  • 记录SSL部署,链路不完整问题
  • Unity Standard Shader 解析(五)之ShadowCaster
  • go 初始化组件最佳实践
  • 2025数学建模国赛高教社杯A题思路代码文章助攻
  • deveco 出现hvigor版本与系统版本不匹配
  • (自用)Linux 常用命令自查文档
  • QT6 配置 Copilot插件
  • 以StarRocks为例讲解MPP架构和列式存储
  • Kafka 学习教程:从基础概念到实践操作
  • 香港云主机常见使用问题汇总
  • 【图像处理基石】图像在频域处理和增强时,如何避免频谱混叠?
  • 【C++】17. AVL树实现
  • Java基础 9.4
  • 市政管网,各种规格的管件汇总大全
  • 【数据模型】思维导图的数据结构模型
  • 力扣字符串刷题-六道题记录-1
  • 【研究前沿】【书读多了,自然就聪明】人工智能中出现的智能涌现的原理是什么?为什么大模型能产生智能?能够泛化?深入了解背后的机制
  • ConvertAPI:PDF转Word的便捷之选
  • 正运动控制卡学习-点动
  • CodeBuddy+Lucene 探索与实践日志:记录我如何从零构建桌面搜索引擎
  • 虚拟化安全:从逃逸漏洞到实战分析
  • 实战演练(二):结合路由与状态管理,构建一个小型博客前台
  • Webus 与中国国际航空合作实现 XRP 支付
  • 专项智能练习(计算机动画基础)
  • webpack scope hositing 和tree shaking
  • AGX Orin平台RTC驱动导致reboot系统卡住问题调试
  • 期权平仓后权利金去哪了?
  • 基于深度掩码的动态模糊处理
  • claude code route 使用教程|命令大全