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组件

Dart 复制代码
final counterProvider = StateProvider<int>((ref) => 0);

class ConsumerWidgetExample extends ConsumerWidget {
  const ConsumerWidgetExample({super.key});

  @override
  Widget 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组件

Dart 复制代码
final counterProvider = StateProvider<int>((ref) => 0);

class ConsumerStatefulWidgetExample extends ConsumerStatefulWidget {
  const ConsumerStatefulWidgetExample({super.key});

  @override
  ConsumerState<ConsumerStatefulWidgetExample> createState() => 
      _ConsumerStatefulWidgetExampleState();
}

class _ConsumerStatefulWidgetExampleState 
    extends ConsumerState<ConsumerStatefulWidgetExample> {
  int _localClicks = 0;
  
  @override
  void initState() {
    super.initState();
    debugPrint('ConsumerStatefulWidget 初始化');
  }
  
  @override
  void dispose() {
    debugPrint('ConsumerStatefulWidget 被销毁');
    super.dispose();
  }

  @override
  Widget 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重建

Dart 复制代码
final counterProvider = StateProvider<int>((ref) => 0);

class ConsumerExample extends ConsumerWidget {
  const ConsumerExample({super.key});

  @override
  Widget 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

Dart 复制代码
final counterProvider = StateProvider<int>((ref) => 0);

class ProviderScopeExample extends StatelessWidget {
  const ProviderScopeExample({super.key});

  @override
  Widget build(BuildContext context) {
    // 创建一个新的provider作用域,可以覆盖父级的provider
    return 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),
              // 在这个作用域内,可以覆盖父级的provider
              Consumer(
                builder: (context, ref, child) {
                  final counter = ref.watch(counterProvider);
                  return Text('计数器: $counter');
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

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

Dart 复制代码
class ProductItem extends StatelessWidget {
    const ProductItem({super.key, required this.index});
    final int index;
    @override
    Widget build(BuildContext context) {
      // do something with the index
    }
  }

class ProductList extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return ListView.builder(
        itemBuilder: (_, index) => ProductItem(index: index),
      );
    }
  }

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

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

在这种情况下:

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

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

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

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

Dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

class HookWidgetExample extends HookWidget {
  const HookWidgetExample({super.key});

  @override
  Widget 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

Dart 复制代码
class HookConsumerWidgetExample extends HookConsumerWidget {
  const HookConsumerWidgetExample({super.key});

  @override
  Widget 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'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
相关推荐
LawrenceLan1 小时前
Flutter 零基础入门(九):构造函数、命名构造函数与 this 关键字
开发语言·flutter·dart
一豆羹2 小时前
macOS 环境下 ADB 无线调试连接失败、Protocol Fault 及端口占用的深度排查
flutter
行者962 小时前
OpenHarmony上Flutter粒子效果组件的深度适配与实践
flutter·交互·harmonyos·鸿蒙
行者965 小时前
Flutter与OpenHarmony深度集成:数据导出组件的实战优化与性能提升
flutter·harmonyos·鸿蒙
小雨下雨的雨5 小时前
Flutter 框架跨平台鸿蒙开发 —— Row & Column 布局之轴线控制艺术
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨6 小时前
Flutter 框架跨平台鸿蒙开发 —— Center 控件之完美居中之道
flutter·ui·华为·harmonyos·鸿蒙
小雨下雨的雨6 小时前
Flutter 框架跨平台鸿蒙开发 —— Icon 控件之图标交互美学
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨6 小时前
Flutter 框架跨平台鸿蒙开发 —— Placeholder 控件之布局雏形美学
flutter·ui·华为·harmonyos·鸿蒙系统
行者967 小时前
OpenHarmony Flutter弹出菜单组件深度实践:从基础到高级的完整指南
flutter·harmonyos·鸿蒙
前端不太难7 小时前
Flutter / RN / iOS,在长期维护下的性能差异本质
flutter·ios