flutter_bloc中BlocSelector的作用是什么?有哪些实际应用场景?
BlocSelector 用法详解与适用场景
BlocSelector 是 flutter_bloc 库中专门用于局部监听 Bloc/Cubit 状态指定字段的组件,核心作用是过滤无关状态变更,只在关注的字段变化时才触发UI重绘,是状态管理性能优化的常用工具。
核心适用场景
- 局部状态依赖场景:只需要响应状态中某一个/几个属性的变化,不需要关心整个状态的其他变更,大幅减少无效重绘
- 细粒度组件拆分场景:页面内多个独立组件依赖同个Bloc的不同状态字段,避免某一个字段变更导致所有组件全部重绘
- 长列表优化场景:每个列表项只依赖单个实体的属性变化,全局状态更新时只有对应数据变化的列表项会重绘,可以显著提升长列表的滑动流畅度
基础使用方法
BlocSelector 接收三个泛型参数,分别是「对应Bloc/Cubit类型」、「状态整体类型」、「你要监听的字段的类型」,基础语法如下:
BlocSelector<对应的Bloc类, 状态类, 监听字段的类型>( // selector 函数:从完整状态里返回你要监听的字段 selector: (state) => state.你要监听的字段, // builder 只接收筛选后的字段值,只有该值变化时才会执行重绘 builder: (context, 筛选后的字段值, child) { // 基于筛选后的字段构建UI即可 return Text(筛选后的字段值.toString()); }, // 可选:传入不需要随状态变化的子组件,进一步优化性能 child: const 不需要重绘的静态组件, )
完整可运行示例
我们以用户信息状态为例,演示实际使用效果:
1. 先定义状态和对应的Cubit
import 'package:flutter_bloc/flutter_bloc.dart'; // 状态类 class UserState { final String name; final int age; final bool isVip; const UserState({ required this.name, required this.age, required this.isVip, }); // 实现copyWith方便生成新状态 UserState copyWith({ String? name, int? age, bool? isVip, }) { return UserState( name: name ?? this.name, age: age ?? this.age, isVip: isVip ?? this.isVip, ); } } // 对应的Cubit class UserCubit extends Cubit<UserState> { UserCubit() : super(const UserState(name: "张三", age: 20, isVip: false)); // 更新年龄 void updateAge(int newAge) { emit(state.copyWith(age: newAge)); } // 切换会员状态 void toggleVip() { emit(state.copyWith(isVip: !state.isVip)); } }
2. 页面中使用BlocSelector
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class UserInfoPage extends StatelessWidget { const UserInfoPage({super.key}); @override Widget build(BuildContext context) { return BlocProvider( create: (context) => UserCubit(), child: Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // 只监听name字段,age、isVip变更不会触发该组件重绘 BlocSelector<UserCubit, UserState, String>( selector: (state) => state.name, builder: (context, name, child) { debugPrint("用户名组件重绘"); return Text("用户名:$name", style: const TextStyle(fontSize: 18)); }, ), const SizedBox(height: 16), // 只监听age字段,name、isVip变更不会触发该组件重绘 BlocSelector<UserCubit, UserState, int>( selector: (state) => state.age, builder: (context, age, child) { debugPrint("年龄组件重绘"); return Text("年龄:$age", style: const TextStyle(fontSize: 18)); }, ), const SizedBox(height: 16), // 只监听isVip字段,name、age变更不会触发该组件重绘 BlocSelector<UserCubit, UserState, bool>( selector: (state) => state.isVip, builder: (context, isVip, child) { debugPrint("会员标识组件重绘"); return Text( isVip ? "已开通会员" : "未开通会员", style: TextStyle( fontSize: 18, color: isVip ? Colors.orange : Colors.grey, ), ); }, ), const SizedBox(height: 32), TextButton( onPressed: () => context.read<UserCubit>().updateAge(21), child: const Text("更新年龄为21"), ), TextButton( onPressed: () => context.read<UserCubit>().toggleVip(), child: const Text("切换会员状态"), ), ], ), ), ), ); } }
运行效果说明
点击「更新年龄为21」按钮时,控制台只会打印年龄组件重绘,用户名和会员标识组件不会触发重绘;点击「切换会员状态」按钮时,控制台只会打印会员标识组件重绘,其他两个组件不会重绘,性能开销远低于使用BlocBuilder监听整个状态。
注意事项
- selector函数必须是纯函数,相同的状态输入必须返回相同的输出,不要在函数内做任何副作用操作
- 如果需要同时监听多个字段,可以返回一个等值的组合对象,比如
selector: (state) => (state.name, state.isVip),只要两个字段都没有变化,就不会触发重绘 - 如果BlocSelector的builder中有不需要随状态变化的静态子组件,可以作为child参数传入,复用该组件进一步提升性能
内容的提问来源于stack exchange,提问作者viki
相关产品推荐
相关产品推荐

