Flutter 把第三方 UI 库渐进迁回 Material 3:组件映射总表 + 四批次替换实战
作者:FungLeo | 适用:Flutter 3.x / Material 3
场景:早期引入的第三方 UI 库不太稳,想换回原生 M3,又不敢一刀切。
本文要点
- 第三方 UI 库不是不能引,但 0.x 版本 + 样式钩子少,长期项目迟早撞墙
- 迁移铁律:先有替代品,再替换,绝不裸删(裸删 = 把设计决策塞进体力活,必乱)
- 动手前
grep摸底 + 产出组件映射总表,把决策前置 - 四批次按风险排序,高频组件反而靠后,每批可独立回滚
- 移除依赖是最后一步:
grep扫全量源码确认零残留,完整重建验证
前言
前文 B01 从 0 搭品牌主题四件套 把主题层搭好了,这篇聊聊怎么把第三方 UI 库的组件一点点换上去。
我们项目早期引了一套第三方 UI 库(TDesign Flutter,0.2.x 版本)。当时想法很朴素:现成组件拿来就用,省时间,何必造轮子。
一开始确实爽。但用了一阵,问题陆续冒出来了:
- 某些组件在 Android 上白屏,换机型又好了,难稳定复现;
- 小版本之间 API 会变,升一次得改一圈调用;
- 视觉细节跟设计稿对不齐,样式没暴露出来,改不动;
- 最要命的,出问题只能等上游修,自己插不上手。
那段时间挺纠结,一开始想"再忍忍"。后来算了笔账------每月因这套库损耗的排查时间,其实已经超过一次性迁移成本了。于是决定:整体迁回 Material 3 原生 + 自研公共组件。
但"换 UI 底层"最忌一次性全改。我见过有团队开个大分支 all in,改三周,合主干时冲突炸裂,测试还没法测------整个 App 都变了,根本不知道该重点回归哪儿。所以我用渐进式四批次替换。这篇把整个过程完整记一下。

第一步:先 grep 摸底,把家底摸清楚
动手前,先搞清楚要改多少东西。别凭感觉,凭感觉估的工作量一般能差三倍。
我的做法是直接 grep 全项目,统计每个三方组件出现次数:
sh
# 统计每个组件出现次数,从多到少排序
grep -rho "TD[A-Za-z]*" lib/ | sort | uniq -c | sort -rn
# 看某个组件具体分布在哪些文件,加 -l
grep -rl "TDButton" lib/
跑出来大概是这么个分布:
sh
🔴 TDButton(~15 处)、TDToast(~20 处) # 高频,影响面最广
🟡 TDTextarea(3)、TDPicker(2)、TDStepper(2)、TDNavBar(1) # 中频
🟢 TDIcons(~20 个不同图标名) # 纯图标,最好换
这张表出来,心里就有底了:真正麻烦的不是数量,而是高频组件牵一发动全身 。TDToast 二十处散在各种异步回调里,改错一个可能就是线上一个静默失败。据此排了替换优先级,并产出一张组件映射总表。
第二步:产出组件映射总表
这一步我强烈建议做,且写成文档存下来,别只放脑子里。因为迁移不是一天干完的,中间会被别的需求打断,三天后回来早忘了 TDPicker 换成啥。多人协作没这张表,两人会替换出两种写法,那还不如不迁。
表大概长这样(节选):
| 旧组件 | M3 / 自研替代 | 备注 |
|---|---|---|
TDIcons.xxx |
Icons.xxx |
一一对应,最简单 |
TDButton(主按钮) |
FilledButton |
圆角走 B01 的主题覆写 |
TDButton(次按钮) |
OutlinedButton / TextButton |
按视觉层级选 |
TDToast |
自研 AppToast |
统一入口,后续 B04 会展开讲公共组件库 |
TDAlertDialog |
自研 AppDialog.confirm |
收敛掉 builder 样板 |
TDNavBar |
AppBar |
主题里已统一样式 |
TDTextarea |
自研 AppTextarea |
带字数计数浮层 |
TDPicker |
showModalBottomSheet + 自绘 |
M3 无直接对应 |
TDStepper |
自绘 | M3 无直接对应 |
映射表里有几个坑,提前说:
一是"一对多"。 像 TDButton,第三方库常用一个组件加 type 参数搞定所有按钮形态;M3 拆成 FilledButton / OutlinedButton / TextButton 三个独立组件。所以你不能无脑替换,得一处一处看它原来是什么形态。这是整个迁移里最费眼力的部分。
二是"没有对应物"。 步进器、日期选择器这些,M3 没提供或形态不合适,只能自绘。这部分工作量最大,单独排批次,别混在常规替换里。
三是行为差异。 这个最阴险。比如三方 Toast 可能"自动排队、后一个等前一个消失",而你用 ScaffoldMessenger 自己实现的是"后一个直接顶掉前一个"。视觉上都是弹提示,但批量操作场景下表现完全不同。这种差异 flutter analyze 查不出,只能靠真机点。

第三步:先搭"替代品",再动手替换
这条我最想强调:不要边删边想新写法。
我一开始就是这么干的------删掉一个 TDToast,现场想"那我用什么呢",随手写了个 ScaffoldMessenger.of(context).showSnackBar(...)。改到第五处,发现前面四处样式、时长、位置全不一样,因为每次都是现场发挥。
正确顺序是:
- 先按 B01 把主题四件套搭好,让原生组件样式先对上设计稿;
- 再把公共组件抽出来(
AppDialog/AppBottomSheet/AppSearchBar/AppToast,后续 B04 细讲); - 放一个 preview 分支,专门做个测试页,把所有形态摆出来验一遍;
- 确认组件质量过关,再回主干逐页替换。
这样替换时你就是个"体力活工人",机械地把旧的换成新的,不需要动脑做设计决策。脑子越不需要动,出错概率越低,这活儿也才安心交给别人一起干。
第四步:四批次渐进替换
批次按风险排,不是按工作量:
| 批次 | 内容 | 风险 |
|---|---|---|
| 1 | 主题搭建 + 组件测试页 | 低 |
| 2 | 低风险:图标 Icons.xxx / AppToast / AppDialog |
低 |
| 3 | 中风险:搜索栏 / 表单区块 / 底部操作栏 | 中 |
| 4 | 自绘:步进器、日期选择器等无直接 M3 对应的 | 中 |
这里有个反直觉的点:高频组件(Button / Toast)影响面最大,为什么反放在靠后批次?
因为影响面大,意味着一旦替代品有问题,波及页面也最多。你得先让替代品在小范围跑一段时间,确认没毛病,再拿它去铺二十个页面。反过来,先铺二十个页面再发现问题,你就得改二十遍。
每一批替换完,跑一遍静态检查再上真机:
sh
# 静态检查,先把编译期和 lint 问题清掉
flutter analyze
# 真机跑一遍,重点看这一批涉及的页面
flutter run
关键是:单批可独立回滚。 每批单独提交、单独合并,哪批出问题 git revert 掉那个提交就行,不影响其他批次。渐进式最大价值不是改得快,是出事时退得起。
第五步:彻底移除依赖
全量替换完成、确认无残留之后,才从 pubspec.yaml 删依赖:
yaml
dependencies:
flutter:
sdk: flutter
# 移除这行(迁移完成后)
# tdesign_flutter: ^0.2.7
sh
flutter pub get
flutter clean && flutter build apk # 完整重建一次,确认无编译残留
删之前,一定要把"零残留"确认干净。我有教训:
确认"零残留"要用
grep扫整个源码 ,而不只看"还有没有 import"。有些地方 import 删了,但注释里、字符串里、甚至某个// TODO: 改成 TDPicker里还留着旧名字。这些不影响编译,但会误导后面接手的人。
sh
# 扫源码,包括注释和字符串
grep -rn "tdesign\|TDesign\|TD[A-Z]" lib/
# 别忘了 pubspec.lock 和各平台目录也扫一眼
grep -rn "tdesign" pubspec.lock ios/ android/
还有个容易忘的:删依赖后一定要完整重建,而不是热重载。 热重载不会重新解析依赖,改完看着正常,等 CI 一跑才发现某个文件还在引用已删的包。我就这么被 CI 打过一次脸。
那我当初到底该不该引这个库?
各位看官可能问:是不是一开始就不该用第三方 UI 库?
不能这么说。事后诸葛亮谁都会,但当时决策在信息条件下合理------项目要快速出原型,现成组件确实省时间。
我现在判断标准大概这样:
- 短周期 / 原型验证:用三方库,快就是一切,反正不长期维护。
- 长期维护项目:如果库版本号还在 0.x,社区活跃度一般,要谨慎。0.x 意味着作者保留随时改 API 的权利,这是人家明说的。
- 设计规范强的项目:三方库样式定制能力是关键。没暴露足够样式钩子,迟早撞墙。
至于"要不要自己造轮子",我的看法:别造完整 UI 库,但可以造一层薄薄的封装。 像后面 B04 那种基于原生 M3 的公共组件,代码量不大、完全可控,出问题自己就能改。这个投入产出比,我觉得划算。
小结
好啦,这套迁移复盘到这。核心几条:
- 迁移 = 先有替代品,再替换,别裸删。删了再想写法,等于把设计决策塞进体力活,必乱。
- 动手前先 grep 摸底 + 产出组件映射总表,把决策前置,替换阶段才能机械化推进。
- 渐进四批次,按风险排序,每批可验证、可回滚,避免"大爆炸式重构"。
- 高频组件放靠后批次。影响面越大,越要等替代品稳了再动。
- 移除依赖是最后一步,grep 扫全量源码确认零残留,完整重建验证。
- 三方库不是不能用,但长期项目要看版本号和定制能力,0.x 版本心里有数。
回头看,这次迁移最大收获不是"用上了 M3",而是把 UI 控制权拿回了自己手里。以前样式问题只能提 issue 等着,现在打开自己的组件文件改两行就完事。这种踏实感,值。
如果这篇对您有点用,希望看官您用发财的小手点个小赞哈!要是您也做过类似的 UI 库迁移,欢迎在评论区分享一下您的批次划分思路,小可在这边谢谢各位看官了哈!
相关阅读
- Flutter 主题色散落七十多处改不全?Material 3 品牌主题四件套实战 ------ B01,本篇前作:先把主题层搭好
- Flutter/Android Release 包连不上网?AndroidManifest INTERNET 权限排查实录 ------ A01,系列起点:release 下第一个"看不见"的坑
- Flutter dart-define 实现 dev/正式双构建,调试代码正式包零残留 ------ A02,前面几篇都用到的编译期开关
- Flutter 接入 Alice 调试浮窗:一个顶层 final 抢跑,把 release 网络整没了 ------ A03,release 下初始化抢跑把网络搞废
- Flutter Android 构建突发红字?一个跟通知无关的库,逼你开 core library desugaring ------ A04,构建期一个不相关的库逼你开 desugaring
- Flutter Debug 红屏、Release 灰屏:你的 release-only bug,只是异常被藏起来了 ------ A05,刚收官的"总 boss":让被吞的异常现形
本文由 FungLeo 主导,Deepseek 优化校阅,转发请注明首发地址,谢谢大家!