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://www.pswp.cn/news/921862.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/921862.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

什么是测试

文章目录软件测试是干什么的&#xff1f;软件测试开发工程师是干什么的&#xff1f;测试工程师是干什么的&#xff1f;软件测试开发工程师和测试工程师的区别效率工具能不能替代测试人员&#xff1f;测开人员的上手路线找工作/实习的时候怎么确定自己找的是测开还是测试呢&…

搭建分片集群

主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决&#xff1a;海量数据存储问题高并发写的问题使用分片集群可以解决上述问题&#xff0c;如图:分片集群特征&#xff1a;集群中有多个master&#xff0c;每个master保存不同数据每个master都可以有多个sla…

在ubuntu系统中如何将docker安装在指定目录

在 Ubuntu 系统中&#xff0c;Docker 默认安装路径&#xff08;程序文件&#xff09;通常在/usr/bin等系统目录&#xff0c;而核心数据&#xff08;镜像、容器、卷等&#xff09;默认存储在/var/lib/docker。若需将数据目录指定到其他位置&#xff08;这是更常见的需求&#xf…

服务器都是用的iis, 前端部署后报跨域,不是用同一个服务器 是前端项目的服务器做Nginx转发,还是后端项目的服务器做Nginx转发?

当服务器环境为 IIS&#xff08;而非 Nginx&#xff09;&#xff0c;且前端、后端部署在不同服务器导致跨域时&#xff0c;核心思路与 Nginx 场景一致&#xff0c;但实现工具从「Nginx」替换为「IIS 配置」。此时依然存在 “后端服务器配置跨域头” 和 “前端服务器配置反向代理…

【大前端】前端生成二维码

前端生成二维码有很多方法&#xff0c;常见的做法是使用 JavaScript 库 来生成二维码。下面整理几种常用方案&#xff0c;并附示例代码。1️⃣ 使用 qrcode 库&#xff08;推荐&#xff09;qrcode 是一个非常流行的前端 JS 库&#xff0c;可以生成 Canvas 或者 SVG 的二维码。安…

LeetCode 刷题【71. 简化路径】

71. 简化路径 自己做 解&#xff1a;遍历检查 class Solution { public:string simplifyPath(string path) {int p 0;string res;while(p < (int)path.size()){//情况1&#xff1a;遇到"/./" 》p跳过"/."if(p < (int)path.size() - 2 && p…

《算法闯关指南:优选算法-双指针》--01移动零,02复写零

&#x1f525;个人主页&#xff1a;草莓熊Lotso &#x1f3ac;作者简介&#xff1a;C研发方向学习者 &#x1f4d6;个人专栏&#xff1a;《C知识分享》《Linux 入门到实践&#xff1a;零基础也能懂》《数据结构与算法》《测试开发实战指南》《算法题闯关指南》 ⭐️人生格言&am…

【小白笔记】命令不对系统:无法将‘head’项识别为 cmdlet、函数、脚本文件或可运行程序的名称

head : 无法将“head”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确保路径正确&#xff0c;然后再试一次。所在位置 行:1 字符: 1 head -5 train_data.csv ~~~~ CategoryInfo : ObjectNotFound: (h…

宋红康 JVM 笔记 Day15|垃圾回收相关算法

一、今日视频区间 P138-P153 二、一句话总结 标记阶段&#xff1a;引用计数算法&#xff1b;标记阶段&#xff1a;可达性分析算法&#xff1b;对象的finalization机制&#xff1b;MAT与JProfiler的GC Roots溯源&#xff1b;清除阶段&#xff1a;标记-清除算法&#xff1b;清除阶…

Go基础(③Cobra)

Cobra 是帮你快速开发命令行工具的框架 假设你想做一个叫 todo 的命令行工具&#xff0c;实现这些功能&#xff1a; todo add "买牛奶" → 添加待办 todo list → 查看所有待办 todo done 1 → 标记第 1 个待办为已完成 没有 Cobra 的话&#xff0c;你需要自己写代…

从 scheduler_tick 到上下文切换:深入解析 Linux 内核的 TIF_NEED_RESCHED 标志设置流程

Linux 是如何决定何时进行上下文切换的&#xff1f; 在Linux中&#xff0c;CPU 上下文切换是指当操作系统将 CPU 从一个进程切换到另一个进程时&#xff0c;保存当前进程的执行状态&#xff0c;并加载新进程的执行状态的过程就称为上下文切换。 但在 Linux 内核中&#xff0c…

Redis 深度解析:数据结构、持久化与集群

Redis (Remote Dictionary Server) 是一种高性能的键值&#xff08;Key-Value&#xff09;内存数据库&#xff0c;以其丰富的数据结构、极低的延迟、出色的稳定性和强大的集群能力&#xff0c;在现代应用程序的开发中扮演着至关重要的角色。无论是作为缓存、消息队列、会话存储…

HTTPS优化简单总结

性能损耗选择椭圆曲线&#xff0c;并生成椭圆曲线的计算耗时CA证书验证的耗时计算pre-master的耗时硬件优化HTTPS是计算密集型任务&#xff0c;不是IO密集型任务所以硬件最好买更高级的CPU&#xff0c;而不是网卡&#xff0c;磁盘协议优化ECDHE代替RSA&#xff0c;因为ECDHE可以…

从IFA再出发:中国制造与海信三筒洗衣机的“答案”

当全球消费电子行业的目光再次聚焦柏林&#xff0c;柏林国际电子消费品展览会(IFA2025)不仅成为创新产品的秀场&#xff0c;更悄然变身为中国企业讲述全球化进阶故事的重要舞台。近日&#xff0c;海信旗下三筒洗衣机——棉花糖Ultra全家筒迎来它的国际首秀&#xff0c;首次海外…

c++工程如何提供http服务接口

在 C 工程里给类似 /index/api/ 的服务&#xff0c;基本步骤如下&#xff1a; 选一个HTTP服务框架&#xff1b;起一条监听线程&#xff08;或线程池&#xff09;&#xff1b;把路径-处理函数注册进去&#xff1b; 下面是 2 种简单的方案。方案 A&#xff1a;Crow&#xff08;He…

cfshow-web入门-php特性

web89 <?php ​ include("flag.php"); highlight_file(__FILE__); ​ if(isset($_GET[num])){$num $_GET[num];if(preg_match("/[0-9]/", $num)){die("no no no!");}if(intval($num)){echo $flag;} } 正则匹配检查不能是数字&#xff0c;但…

ctfshow - web - 命令执行漏洞总结(二)

web73该题目没有开启web72的open_basedir&#xff0c;所以可以使用var_export(scandir(/));exit();进行目录扫描。读取文件函数&#xff1a;require_once()web74scandir()函数被禁用&#xff0c;使用glob://伪协议进行读取根目录文件。cvar_export(glob(../../../*));exit(); c…

如何将视频从安卓手机传输到电脑?

无论你是否是视频爱好者&#xff0c;你可能都希望知道如何将视频从安卓手机传输到电脑&#xff0c;以释放存储空间并防止性能问题。这也有助于同步视频或防止意外删除。在本文中&#xff0c;我们将探索七种高效的传输方法。方法 1&#xff1a;仅通过 USB 将手机视频发送到电脑许…

Pico 4 Enterprise(企业版)与Unity的交互-有线串流调试篇

入手了Pico 4 E做VR开发&#xff0c;谁知入了天坑...根据官方文档&#xff0c;尝试了串流助手、企业串流、PICO Developer Center&#xff0c;陷入了各种版本问题、环境问题的陷阱。而且Pico4E的OS自24年12开始就不再更新&#xff0c;头盔中预装的企业串流版本也较低&#xff0…

redis里多线程的应用具体在哪些场景

Redis 6.0 引入的多线程I/O&#xff0c;​特指用于处理网络数据的读取&#xff08;read&#xff09;和写入&#xff08;write&#xff09;/解析&#xff08;parse&#xff09;的并行化&#xff0c;而绝非将命令的执行&#xff08;真正的数据操作&#xff09;变成多线程。这是一…