跨页面存数据时,能否不发射新状态仅用单一状态使用Cubit?
问题描述
我需要在多个页面间留存数据,页面流程为MainPage→PageA→PageB→PageC→PageD→Summary。用户在各页面选择的数据需被存储,支持回退修改,最终在Summary页提交至API。
我创建了如下ParametersCubit类,但未实际使用状态,直接在Cubit中定义变量:
class ParametersCubit extends Cubit<ParametersState> { ParametersCubit() : super(ParametersStateInitial()); BoxParams? boxParams; bool? privateRentier; Map<dynamic, int> mapOfSelectedIds = {}; Map<dynamic, BoxSize?> mapOfSizes = {}; Map<dynamic, Variants> mapOfVariants = {}; DateTime selectedDate = DateTime.now(); DateTime? selectedRangeDate = DateTime.now().add(Duration(days: 14)); void initParameters(BoxParams selectedBox, bool? privRentier) { clearParameters(); boxParams = selectedBox; privateRentier = privRentier; } void setMapParameters(Map<dynamic, int> ids, Map<dynamic, BoxSize?> sizes, Map<dynamic, Variants> variants) { mapOfSelectedIds = ids; mapOfSizes = sizes; mapOfVariants = variants; } void clearParameters() { boxParams = null; privateRentier = null; mapOfSizes = {}; mapOfSelectedIds = {}; mapOfVariants = {}; selectedDate = DateTime.now(); selectedRangeDate = DateTime.now().add(Duration(days: 14)); } void rentBox() { //TODO send request to API } }
对应的状态类如下:
abstract class ParametersState {} class ParametersStateInitial extends ParametersState {}
我通过context.read()和context.watch()直接访问修改Cubit中的变量,例如:
class SelectDate extends StatefulWidget { const SelectDate({Key? key}) : super(key: key); @override State<SelectDate> createState() => _SelectDateState(); } class _SelectDateState extends State<SelectDate> { @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; final local = AppLocalizations.of(context)!; return Scaffold( body: SafeArea( child: MainPadding( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'From when you want to rent?', style: TextStyle( fontSize: 20, fontWeight: FontWeight.w600, color: Color(0xFF1B1B1B) ), ), const SizedBox(height: 64), CalendarDatePicker2( config: CalendarDatePicker2Config( calendarType: CalendarDatePicker2Type.single, firstDayOfWeek: 1, firstDate: DateTime.now(), weekdayLabels: [ local.sunday, local.monday, local.tuesday, local.wednesday, local.thursday, local.friday, local.saturday, ] ), value: [ context.watch<ParametersCubit>().selectedDate ], onValueChanged: (date) { context.read<ParametersCubit>().selectedDate = date.single; }, ) ] ), ), ), ); } }
当前功能正常,但我怀疑这不符合Cubit/Bloc的正确用法。请问我是否需要创建对应状态?或者有其他更合适的方案?还是当前用法并无问题?
回答
你的当前写法不符合Cubit的设计规范,虽然暂时能运行,但会埋下不少隐患,下面拆解问题并给出优化方案:
一、当前写法的核心问题
- 破坏单向数据流:Cubit的核心逻辑是通过
emit发布状态变化,UI组件监听状态更新。直接修改Cubit的成员变量,跳过了状态发布流程,违反了Cubit的设计原则。 - UI更新不可靠:
context.watch<ParametersCubit>()能生效只是依赖Cubit内部的监听机制,但修改变量时未触发状态变更,后续Cubit逻辑调整可能导致UI无法同步更新。 - 调试难度高:Cubit/Bloc的DevTools可以追踪状态变化,但直接修改变量的操作不会被记录,排查问题时很难回溯数据变化过程。
- 状态一致性风险:多个地方同时修改Cubit变量时,没有统一的状态发布机制,容易出现数据不一致的情况。
二、正确的Cubit实现方式
需要把所有共享数据封装到不可变的状态类中,通过Cubit方法修改数据后emit新状态。
1. 重构状态类
创建包含所有参数的不可变状态类,并提供copyWith方法方便修改部分状态:
abstract class ParametersState {} class ParametersStateInitial extends ParametersState {} class ParametersStateLoaded extends ParametersState { final BoxParams? boxParams; final bool? privateRentier; final Map<dynamic, int> mapOfSelectedIds; final Map<dynamic, BoxSize?> mapOfSizes; final Map<dynamic, Variants> mapOfVariants; final DateTime selectedDate; final DateTime? selectedRangeDate; ParametersStateLoaded({ this.boxParams, this.privateRentier, required this.mapOfSelectedIds, required this.mapOfSizes, required this.mapOfVariants, required this.selectedDate, this.selectedRangeDate, }); ParametersStateLoaded copyWith({ BoxParams? boxParams, bool? privateRentier, Map<dynamic, int>? mapOfSelectedIds, Map<dynamic, BoxSize?>? mapOfSizes, Map<dynamic, Variants>? mapOfVariants, DateTime? selectedDate, DateTime? selectedRangeDate, }) { return ParametersStateLoaded( boxParams: boxParams ?? this.boxParams, privateRentier: privateRentier ?? this.privateRentier, mapOfSelectedIds: mapOfSelectedIds ?? this.mapOfSelectedIds, mapOfSizes: mapOfSizes ?? this.mapOfSizes, mapOfVariants: mapOfVariants ?? this.mapOfVariants, selectedDate: selectedDate ?? this.selectedDate, selectedRangeDate: selectedRangeDate ?? this.selectedRangeDate, ); } }
2. 重构Cubit类
移除Cubit中的成员变量,所有数据通过状态管理,修改后发布新状态:
class ParametersCubit extends Cubit<ParametersState> { ParametersCubit() : super(ParametersStateInitial()); void initParameters(BoxParams selectedBox, bool? privRentier) { emit(ParametersStateLoaded( boxParams: selectedBox, privateRentier: privRentier, mapOfSelectedIds: {}, mapOfSizes: {}, mapOfVariants: {}, selectedDate: DateTime.now(), selectedRangeDate: DateTime.now().add(const Duration(days: 14)), )); } void setMapParameters(Map<dynamic, int> ids, Map<dynamic, BoxSize?> sizes, Map<dynamic, Variants> variants) { if (state is ParametersStateLoaded) { emit((state as ParametersStateLoaded).copyWith( mapOfSelectedIds: ids, mapOfSizes: sizes, mapOfVariants: variants, )); } } void updateSelectedDate(DateTime date) { if (state is ParametersStateLoaded) { emit((state as ParametersStateLoaded).copyWith(selectedDate: date)); } } void updateSelectedRangeDate(DateTime date) { if (state is ParametersStateLoaded) { emit((state as ParametersStateLoaded).copyWith(selectedRangeDate: date)); } } void clearParameters() { emit(ParametersStateLoaded( boxParams: null, privateRentier: null, mapOfSelectedIds: {}, mapOfSizes: {}, mapOfVariants: {}, selectedDate: DateTime.now(), selectedRangeDate: DateTime.now().add(const Duration(days: 14)), )); } void rentBox() { if (state is ParametersStateLoaded) { final currentState = state as ParametersStateLoaded; // 从当前状态获取数据,调用API提交 // TODO: send request to API with currentState data } } }
3. 修改UI组件用法
通过监听状态获取数据,调用Cubit方法触发状态更新:
class SelectDate extends StatefulWidget { const SelectDate({Key? key}) : super(key: key); @override State<SelectDate> createState() => _SelectDateState(); } class _SelectDateState extends State<SelectDate> { @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; final local = AppLocalizations.of(context)!; // 从状态中获取当前选中的日期 final selectedDate = context.watch<ParametersCubit>().state is ParametersStateLoaded ? (context.watch<ParametersCubit>().state as ParametersStateLoaded).selectedDate : DateTime.now(); return Scaffold( body: SafeArea( child: MainPadding( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'From when you want to rent?', style: TextStyle( fontSize: 20, fontWeight: FontWeight.w600, color: Color(0xFF1B1B1B) ), ), const SizedBox(height: 64), CalendarDatePicker2( config: CalendarDatePicker2Config( calendarType: CalendarDatePicker2Type.single, firstDayOfWeek: 1, firstDate: DateTime.now(), weekdayLabels: [ local.sunday, local.monday, local.tuesday, local.wednesday, local.thursday, local.friday, local.saturday, ] ), value: [selectedDate], onValueChanged: (date) { // 调用Cubit方法修改日期,触发状态更新 context.read<ParametersCubit>().updateSelectedDate(date.single); }, ) ] ), ), ), ); } }
三、其他可选方案
如果觉得Cubit的状态封装过于繁琐,也可以考虑以下替代方案:
ChangeNotifier:适合简单状态管理,通过notifyListeners()触发UI更新,代码量更少,但调试和状态追踪能力不如Cubit。Riverpod:更灵活的状态管理方案,支持多种状态类型,无需依赖BuildContext即可访问状态,适合复杂场景。
但如果已经选择了Cubit,建议遵循其设计规范,充分利用它的状态追踪、调试等优势,避免后续维护问题。
内容的提问来源于stack exchange,提问作者Karol Wiśniewski
相关产品推荐
相关产品推荐

