Flutter之riverpod状态管理Widget UI详解

发布于:2025-09-05 ⋅ 阅读:(20) ⋅ 点赞:(0)

一、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});

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

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重建

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

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或索引时:

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的值:

// 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来管理计数值:

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

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'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}


网站公告

今日签到

点亮在社区的每一天
去签到