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
英文地址,請注明出處:http://en.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;變成多線程。這是一…