

子玥酱 (掘金 / 知乎 / CSDN / 简书 同名)
大家好,我是 子玥酱,一名长期深耕在一线的前端程序媛 👩💻。曾就职于多家知名互联网大厂,目前在某国企负责前端软件研发相关工作,主要聚焦于业务型系统的工程化建设与长期维护。
我持续输出和沉淀前端领域的实战经验,日常关注并分享的技术方向包括 前端工程化、小程序、React / RN、Flutter、跨端方案,
在复杂业务落地、组件抽象、性能优化以及多端协作方面积累了大量真实项目经验。
技术方向: 前端 / 跨端 / 小程序 / 移动端工程化 内容平台: 掘金、知乎、CSDN、简书 创作特点: 实战导向、源码拆解、少空谈多落地 **文章状态:**长期稳定更新,大量原创输出
我的内容主要围绕 前端技术实战、真实业务踩坑总结、框架与方案选型思考、行业趋势解读 展开。文章不会停留在"API 怎么用",而是更关注为什么这么设计、在什么场景下容易踩坑、真实项目中如何取舍,希望能帮你在实际工作中少走弯路。
子玥酱 · 前端成长记录官 ✨
👋 如果你正在做前端,或准备长期走前端这条路
📚 关注我,第一时间获取前端行业趋势与实践总结
🎁 可领取 11 类前端进阶学习资源 (工程化 / 框架 / 跨端 / 面试 / 架构)
💡 一起把技术学"明白",也用"到位"
持续写作,持续进阶。
愿我们都能在代码和生活里,走得更稳一点 🌱
文章目录
-
- 引言
- 状态复杂度不是突然爆炸的,而是"慢慢失控"的
-
- [页面状态写在 Widget 里](#页面状态写在 Widget 里)
- 状态开始被多个地方使用
- 状态集中,但复杂度没降
- 问题不在"状态放哪",而在"状态边界"
- 一个简单但非常有效的"状态生命周期划分法"
- 禁止"状态无理由上提"
- [UI 不直接"写业务状态"](#UI 不直接“写业务状态”)
- 防止状态"越管越大"
- [为什么 iOS 项目"看起来更稳"?](#为什么 iOS 项目“看起来更稳”?)
- [一套真正"抗演进"的 Flutter 状态架构原则](#一套真正“抗演进”的 Flutter 状态架构原则)
- 总结
引言
很多 Flutter 项目,不是一开始就乱的。
刚启动的时候,结构清晰、状态不多、页面逻辑简单,大家都觉得 Flutter 写起来挺爽。
但过一段时间你会发现:
- Provider 越加越多
- 页面逻辑越来越厚
- 一个需求改动,牵一堆地方
最后大家会下意识地把锅甩给一句话:
Flutter 的状态管理太乱了。
但如果你回头复盘,大多数问题并不是状态管理框架选错了,而是:
架构层从一开始,就没有给"状态复杂度"留刹车空间。
状态复杂度不是突然爆炸的,而是"慢慢失控"的
页面状态写在 Widget 里
dart
class ProductPage extends StatefulWidget {
@override
State<ProductPage> createState() => _ProductPageState();
}
class _ProductPageState extends State<ProductPage> {
bool loading = false;
List<String> products = [];
Future<void> loadData() async {
setState(() => loading = true);
await Future.delayed(Duration(seconds: 1));
setState(() {
products = ['Apple', 'Banana'];
loading = false;
});
}
}
一切看起来都很合理。
状态开始被多个地方使用
- 列表页要用
- 详情页要用
- 刷新、分页、筛选都要用
于是你做了第一步"优化":
把状态提取到 Provider / Riverpod 里。
状态集中,但复杂度没降
dart
class ProductState extends ChangeNotifier {
bool loading = false;
List<String> products = [];
String keyword = '';
int page = 1;
Future<void> load() async {
loading = true;
notifyListeners();
await Future.delayed(Duration(seconds: 1));
products.addAll(['Apple', 'Banana']);
loading = false;
notifyListeners();
}
}
看起来状态"集中"了,但你很快会发现:
- 所有页面都在监听这个 State
- 一点小变动,整个页面树都在 rebuild
- 状态文件越来越像一个"垃圾桶"
这一步,才是大多数 Flutter 项目真正开始失控的地方。
问题不在"状态放哪",而在"状态边界"
Flutter 的状态复杂度,本质上不是数量问题,而是边界问题。
一个很关键但常被忽略的问题
这个状态,生命周期到底有多长?
如果你不在架构层回答这个问题,后面一定会付出代价。
一个简单但非常有效的"状态生命周期划分法"
UI 临时状态
- loading 动画开关
- tab index
- checkbox 勾选
原则:离 UI 越近越好
dart
bool _loading = false;
页面级状态
- 列表数据
- 表单内容
- 页面筛选条件
只服务于一个页面或一个功能域
全局状态
- 登录态
- 用户信息
- 系统配置
必须极少、极稳、极慎重
很多 Flutter 项目"炸",就是因为把页面状态当成了全局状态。
禁止"状态无理由上提"
一个非常重要的架构原则:
状态不应该因为"方便",就被提升到更高层级。
错误示例:页面状态直接全局化
dart
final productProvider = ChangeNotifierProvider(
create: (_) => ProductState(),
);
然后所有页面都 watch 它。
这在前期看起来很爽,但后期会带来两个问题:
- rebuild 范围不可控
- 状态职责开始模糊
正确做法:状态按功能域隔离
dart
class ProductModule extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => ProductState(),
child: ProductPage(),
);
}
}
- 状态跟着功能走
- 页面销毁,状态一起销毁
- 不给复杂度"扩散"的机会
是 Flutter 架构中非常重要的一脚刹车。
UI 不直接"写业务状态"
很多 Flutter 项目后期难维护,是因为 UI 层承担了太多职责。
反面示例
dart
onPressed: () async {
state.loading = true;
notifyListeners();
await api.fetch();
state.products.addAll(result);
state.loading = false;
notifyListeners();
}
UI 知道太多了。
正确做法:状态自己对外暴露"意图"
dart
class ProductState extends ChangeNotifier {
Future<void> refresh() async {
_setLoading(true);
await _loadData();
_setLoading(false);
}
}
UI 只做一件事:
dart
onPressed: state.refresh
UI 只表达"我要做什么",
不关心"你是怎么做的"。
防止状态"越管越大"
一个非常实用的判断标准:
如果一个 State 文件开始超过 300 行,基本已经失控。
这时你需要做的不是换框架,而是拆状态。
拆分示例
dart
class ProductListState {}
class ProductFilterState {}
class ProductPagingState {}
再由一个组合层统一协调。
dart
class ProductViewModel {
final list = ProductListState();
final filter = ProductFilterState();
}
本质上和前端拆 reducer、iOS 拆 ViewModel 是一回事。
为什么 iOS 项目"看起来更稳"?
因为 UIKit / MVC 帮你强制做了很多刹车:
- 页面生命周期固定
- Controller 天然是边界
- 状态默认不跨页面
而 Flutter / 前端给了你极大的自由,但不会替你踩刹车。
一套真正"抗演进"的 Flutter 状态架构原则
如果你只记住这一段就够了:
- 状态永远有生命周期
- 生命周期决定状态归属
- 页面状态默认不全局
- UI 不写业务流程
- 复杂不是错,失控才是错
总结
Flutter 状态管理真正难的地方,从来不是:
Provider 还是 Riverpod?
Bloc 会不会太重?
而是:
你有没有在架构层,提前为复杂度准备"刹车系统"。
如果没有,再好的状态管理方案,最后都会变成负担。