不知道你有没有过这种"既视感":每次开一个新 Flutter 项目,都得把那一套"祖传代码"再复制粘贴一遍。
配网络请求 Dio、设路由、封装几个通用 Widget、搞个状态管理......一套流程下来,半天过去了,真正的业务代码,一行都还没写。这感觉,就像你准备做一桌满汉全席,结果光是淘米、洗菜、磨刀就花了大半天,累得够呛。
这堆重复劳动,我称之为"开发税"------每个项目都得交,还不得不交。
后来我带的团队项目多了,这个问题就更严重了。A 同学封装的网络请求,B 同学用不惯;C 同学写的组件,D 同学那边又改一版。代码规范?不存在的。效率?全看个人手感。
不行,这税得想办法给它"免"了。
于是,我花了点时间,沉淀了一套自己的 Flutter 快速开发框架。今天,我就把这套思路掰开揉碎了分享给你,让你也能搭一套自己的"轮子",告别重复劳动,把精力真正聚焦在业务上。
一、从各自为战到统一思想:我的"翻车"与顿悟
一开始,我的想法很简单:把常用的工具类、网络请求封装一下,打个包不就完事了?
结果呢?第一个项目还行,第二个项目开始魔改,到了第三个,已经跟第一版亲妈都不认识了。究其原因,就是缺少一个核心设计思想。
后来我才悟了,一个好的框架,不是功能的堆砌,而是规则的建立 。它的灵魂就八个字:"约定优于配置"(Convention over Configuration)。
啥意思?
就是说,框架要替你"规定"好,什么文件放哪里、什么模块叫什么名。比如,只要你把一个页面放在 features/
目录下,框架就应该能自动帮你注册好路由。你不用去手动配置,框架通过"约定"就帮你搞定了。
基于这个思想,整个框架的设计就清晰了:
- 高内聚,低耦合:每个模块像乐高积木,只干一件事,可以随时替换。今天用 Dio,明天想换 http,拔掉重插就行。
- 插件化:功能即插件。需要地图就加个地图插件,不需要就不加,保持核心的轻量。
- 只关心业务:把网络、缓存、路由这些脏活累活全藏在底层,暴露给业务开发的,是极其简单的接口。
二、蓝图在手,天下我有:框架的整体架构
光有理念不行,得有张蓝图。下面这张图,就是我打磨出来的项目架构,清晰明了,新手照着搭都不会错。

我给你翻译一下:
core/
(核心能力) :这是框架的发动机。网络、路由、状态管理、缓存、日志这些"脏活累活",全部扔这里。业务开发者原则上不应该直接修改这里面的代码。common/
(通用资产):这是团队的"共享工具箱"。通用的按钮、加载框、工具类、主题样式,都在这儿。features/
(业务模块):这才是我们每天打交道的主战场。每个功能模块(比如登录、首页、设置)都是一个独立的文件夹,高内聚。generated/
(自动生成):所有由工具自动生成的文件,比如路由表,都放这。和手写代码隔离,非常清爽。
这套结构,就像一个城市的规划:主干道(core)、公共设施(common)、各个小区(features),分工明确,井井有条。
三、庖丁解牛:几个关键模块我是怎么设计的
蓝图有了,我们来聊聊几个关键"零件"是怎么打磨的。
1. 路由系统:告别手写路由表
- 我踩过的坑 :一开始用官方的
MaterialPageRoute
,每次跳转都手写一大坨。页面多了,参数传递能把人逼疯,简直是"屎山"的重灾区。 - 我的理解:路由应该像"查字典",我告诉你去哪(命名路由),你就自动帮我带路,别让我操心怎么走。
- 最终方案 :采用注解 + 代码生成。我强烈推荐
auto_route
这个库。
你只需要在页面类上加个注解:
dart
@RoutePage()
class LoginPage extends StatelessWidget { ... }
然后跑个命令,它就会自动生成带参数的、类型安全的路由方法。用起来有多爽?
dart
// 告别 "Navigator.pushNamed('/login', arguments: ...)"
// 拥抱类型安全
AppNavigator.push(LoginRoute(userName: "LaoG"));
跳转、传参、返回,一个方法搞定,优雅,太优雅了!
2. 网络请求:建一个"中央厨房"
- 我踩过的坑 :每个页面都自己创建一个
Dio
实例,到处都是try-catch
。后端一说 token 失效要重新登录,我就得去几十个文件里改代码,改到最后,自己都不知道改没改全。 - 我的理解:网络请求是典型的"横切关注点",它需要一个统一的"哨兵"或"网关"来处理公共逻辑。
- 最终方案 :封装一个全局单例的
Http
类,把Dio
包在里面。重点是玩转拦截器(Interceptor)。
我的拦截器链通常是这样的:
- 日志拦截器:Debug 环境下打印详细的请求、响应日志,甩锅(哦不,定位问题)必备。
- 请求头拦截器 :统一加上
Content-Type
、Token
等。 - 响应拦截器 :在这里做统一的业务错误码处理。比如
code: 401
就直接弹窗提示并跳转到登录页,页面代码里根本不用关心这事儿! - 错误拦截器:处理网络超时、DNS 解析失败等异常,给用户一个友好的提示。
这样一来,业务代码里的网络请求就变得极其纯粹:
dart
// 业务层只需要这样调用,完全不关心 Token 和错误处理
final user = await Http.instance.get('/api/user/1');
3. 状态管理:给你的页面装个"大脑"
- 我踩过的坑 :从
setState
到Provider
,一开始觉得挺好,但业务一复杂,UI 和业务逻辑就搅在一起,跟一团浆糊似的。状态满天飞,想找个 bug,得从 UI 代码里一点点往回捋。 - 我的理解 :UI 应该是个"傻子",它只负责展示数据和发送指令。真正的思考和数据处理,应该交给一个专门的"大脑"------
ViewModel
。 - 最终方案 :推荐使用
Riverpod
+MVVM
模式。
Riverpod
的好处在于它天生的依赖注入和更清晰的生命周期管理,非常适合解耦。我会创建一个 BaseViewModel
,内置几种通用状态:
dart
enum ViewState { idle, loading, error, empty }
abstract class BaseViewModel extends StateNotifier<ViewState> {
// ... 内置加载、错误处理等通用方法 ...
Future<void> safeAction(Future<void> Function() action) async {
state = ViewState.loading;
try {
await action();
state = ViewState.idle;
} catch (e) {
state = ViewState.error;
}
}
}
每个页面的 ViewModel
都继承它,天生就拥有了加载中、错误、空页面的状态管理能力。
四、锦上添花:用命令行工具解放生产力
框架搭好了,怎么让它用起来更爽?答案是:命令行工具。
我用的是 mason 这个库,它可以让你创建自己的代码模板。比如,我想创建一个新的登录页面,只需要在终端敲一行命令,当然我的做法是直接集成到 vscode 插件中,这样对着 feature 右键,就可以创建各种,何止是页面呀,想啥是啥:
bash
mason make page login
然后,mason
就会自动帮我创建好:
bash
features/
└── login/
├── view/login_page.dart
├── view_model/login_vm.dart
└── widgets/
连 ViewModel
的继承、页面的基础结构都写好了!这感觉,就像从手动挡换到了自动挡,谁用谁知道。
落地实践建议
看到这里,你可能有点心动,但又觉得工程量太大。别慌,罗马不是一天建成的。
- 从封装一个模块开始:先别急着搭整个架子。在你下个项目中,先尝试只封装一个完美的网络请求模块。
- 建立你的 Starter 项目 :把你的最佳实践沉淀到一个 GitHub 仓库里,创建一个
flutter-starter-template
。以后开新项目,直接git clone
就行。 - 团队布道:如果你在团队里,把这套东西分享给同事,形成规范。统一的规范,是提升团队整体效率的大杀器。
好了,今天就先聊到这。框架不是目的,效率才是。希望这套思路能帮你摆脱"开发税",把更多的时间和才华,投入到创造真正有价值的业务功能中去。
延伸阅读推荐:
- Riverpod 官网 : riverpod.dev/ (状态管理的未来,值得深入研究)
- auto_route 官方文档 : pub.dev/packages/au... (感受一下注解式路由的魅力)