Flutter 如何设计可长期维护的模块边界?


子玥酱 (掘金 / 知乎 / CSDN / 简书 同名)

大家好,我是 子玥酱,一名长期深耕在一线的前端程序媛 👩‍💻。曾就职于多家知名互联网大厂,目前在某国企负责前端软件研发相关工作,主要聚焦于业务型系统的工程化建设与长期维护。

我持续输出和沉淀前端领域的实战经验,日常关注并分享的技术方向包括 前端工程化、小程序、React / RN、Flutter、跨端方案,

在复杂业务落地、组件抽象、性能优化以及多端协作方面积累了大量真实项目经验。

技术方向: 前端 / 跨端 / 小程序 / 移动端工程化 内容平台: 掘金、知乎、CSDN、简书 创作特点: 实战导向、源码拆解、少空谈多落地 **文章状态:**长期稳定更新,大量原创输出

我的内容主要围绕 前端技术实战、真实业务踩坑总结、框架与方案选型思考、行业趋势解读 展开。文章不会停留在"API 怎么用",而是更关注为什么这么设计、在什么场景下容易踩坑、真实项目中如何取舍,希望能帮你在实际工作中少走弯路。

子玥酱 · 前端成长记录官 ✨

👋 如果你正在做前端,或准备长期走前端这条路

📚 关注我,第一时间获取前端行业趋势与实践总结

🎁 可领取 11 类前端进阶学习资源 (工程化 / 框架 / 跨端 / 面试 / 架构)

💡 一起把技术学"明白",也用"到位"

持续写作,持续进阶。

愿我们都能在代码和生活里,走得更稳一点 🌱

文章目录

引言

很多 Flutter 项目在前期其实并不缺"结构"。甚至可以说,大多数项目一开始都有一套看起来很合理的目录:

复制代码
pages / widgets / services / models

问题不是有没有结构,而是:

这套结构,无法支撑业务持续变化。

当项目进入中后期,你会频繁遇到这些情况:

  • 一个需求要改 5 个目录
  • 一个模块改动影响多个页面
  • 想删一个功能,却发现删不干净

这些问题的本质,其实只有一个:

模块边界从一开始就没有设计好。

什么是"好的模块边界"

很多人理解模块边界,是"文件放在哪"。但真正有效的边界,应该解决三个问题:

  • 哪些代码可以改
  • 哪些代码不能随便动
  • 改动会影响到哪里

换句话说,一个好的模块边界应该具备:

可独立理解

不用跳多个目录,就能看懂一个功能

可独立修改

修改一个模块,不需要同步改其他模块

可独立删除

删除一个功能,不会留下"幽灵依赖"

如果做不到这三点,说明边界只是"看起来存在"。

为什么 Flutter 项目很容易边界失效

Flutter 在语法和工程上都非常"自由",这种自由在前期是优势,但也埋下了隐患。

import 没有限制

复制代码
import '../../services/order_service.dart';

任何地方都可以直接访问任何模块,结果就是:

  • UI 可以直接调 data 层
  • 工具类被全局依赖
  • 不同业务之间相互调用

边界一旦被跨越,就很难再收回来。

目录结构不等于模块结构

很多项目看起来分层清晰:

复制代码
pages/
services/
models/

但实际依赖是这样的:

复制代码
page A → service B → model C
page D → service B → model C

本质上:

这是按技术分层,而不是按业务分模块。

当业务增长,这种结构一定会交叉。

过早"抽公共",反而破坏边界

很多团队会很早建立:

复制代码
common/
shared/
utils/

初衷是复用,但结果往往是:

  • shared 越来越大
  • 所有模块都依赖它
  • 修改风险越来越高

最后变成一个典型问题:

公共层成了最大的耦合源。

一个更稳定的模块划分方式:按 Feature 拆

更适合长期维护的方式,是按业务拆分:

复制代码
lib/
 ├─ core/
 ├─ shared/
 └─ features/
     ├─ user/
     ├─ order/
     └─ home/

这里的关键点不是目录,而是边界规则

每个 Feature 必须"自闭环"

一个完整的 feature,应该长这样:

复制代码
order/
 ├─ data/
 │   ├─ order_api.dart
 │   ├─ order_dto.dart
 │   └─ order_repository.dart
 ├─ domain/
 │   └─ order_entity.dart
 ├─ ui/
 │   ├─ order_page.dart
 │   └─ widgets/
 └─ order_router.dart

核心原则:

一个功能的所有实现,都在一个目录内完成。

这样带来的好处是:

  • 新人只看一个目录就能理解功能
  • 改动范围天然被限制
  • 删除功能变得可行

依赖必须单向流动

必须明确一条规则:

复制代码
UI → Domain → Data

禁止:

  • data 调 UI
  • feature 之间直接调用内部实现

如果需要跨模块通信,可以用:

  • 抽象接口
  • 事件机制
  • 路由参数

本质是:

依赖只能向内收敛,不能横向扩散。

shared 只放"真正无业务"的代码

很多项目最大的问题就是 shared 失控,一个更严格的约束是,shared 只允许存在两类东西:

  • 完全无业务语义的 UI(比如基础按钮)
  • 纯工具函数(无状态、无依赖)

举个反例:

复制代码
UserCardWidget  (有业务语义)

正确做法是:

这种组件必须放在 user feature 内。

否则边界会逐渐被侵蚀。

如何判断边界是否健康

你可以用三个简单的问题做自检:

问题一:删除一个 feature,能否不改其他代码?

如果不能,说明存在隐性依赖。

问题二:新增一个功能,需要改多少旧代码?

如果修改范围很大,说明边界不清晰。

问题三:一个 bug 是否能快速定位模块?

如果需要全局搜索,说明结构已经失控。

一个常见误区:过度模块化

有些团队意识到问题后,会走向另一个极端:

  • 拆很多 package
  • 引入复杂依赖管理
  • 强行做"组件化"

结果是:

  • 开发效率下降
  • 调试成本变高
  • 团队难以适应

需要明确一点:

模块化不是目的,降低复杂度才是。

如果业务还没复杂到一定程度,过度设计反而是负担。

实际项目中的落地建议

结合实际开发经验,可以从这几个步骤开始优化:

第一步:停止扩展 shared

所有新功能优先放 feature 内

第二步:逐步迁移老代码

按业务把代码往 feature 收拢

第三步:在 code review 中约束依赖方向

这是最关键的一步

第四步:控制模块大小

一个 feature 太大时,再内部细分,而不是拆到全局

总结

模块边界这件事,很少有人在项目初期认真设计,但几乎所有团队都会在后期为此付出代价。

Flutter 给了我们足够的灵活性,但也意味着:

边界不会自动出现,只能靠人为约束。

当你开始用"业务"而不是"文件类型"去划分模块,用"依赖方向"而不是"目录层级"去约束代码时,项目才会真正从"能写",走向"能长期维护"。

相关推荐
小蜜蜂嗡嗡3 小时前
flutter列表中实现置顶动画
flutter
始持3 小时前
第十二讲 风格与主题统一
前端·flutter
始持3 小时前
第十一讲 界面导航与路由管理
flutter·vibecoding
始持3 小时前
第十三讲 异步操作与异步构建
前端·flutter
新镜4 小时前
【Flutter】 视频视频源横向、竖向问题
flutter
黄林晴4 小时前
Compose Multiplatform 1.10 发布:统一 Preview、Navigation 3、Hot Reload 三箭齐发
android·flutter
Swift社区5 小时前
Flutter 应该按功能拆,还是按技术层拆?
flutter
肠胃炎5 小时前
树形选择器组件封装
前端·flutter
程序员老刘20 小时前
跨平台开发地图:金三银四你准备好了吗? | 2026年3月
flutter·客户端