首页真正开始变重,往往不是 tab 能不能动态,而是它一边要处理远端配置,一边要处理登录态差异和多入口跳转。用户从不同地方进来,默认先到哪一页、旧状态要不要保、资源位和不同内容往哪儿走,都会一起变。走到这一步,首页处理的就不只是页面显示,而是整套跳转和页面安排。
我这次重新看首页相关代码,最先让我停住的,不是动态 tab,也不是某个配置字段,而是一段跳转逻辑。
很多入口并不是直接去目标页,而是要先退回首页,再继续往下走:
dart
if (currentRoute != AppRoutes.initial && currentRoute != AppRoutes.splash) {
Get.until((route) => route.settings.name == AppRoutes.initial);
}
Get.toNamed(AppRoutes.gameDetail, arguments: args);
如果首页真的只是一个普通页面,这种写法其实很奇怪。
你明明可以直接跳去详情页,为什么还要先回首页?
只有一种解释说得通:
首页在这个项目里,早就不只是首页了。
它已经成了很多复杂跳转最后都要借一下力的固定落点。
顺着这段逻辑再往下看,后面很多原本零散的问题就会重新连起来:
- 为什么同样是首页,从不同地方进来体验不一样
- 为什么某些 tab 一改,旧状态就开始乱
- 为什么资源位、Deep Link、小程序和首页会互相牵扯
- 为什么启动阶段很多初始化最后也会绕回首页这套逻辑里
所以这篇文章真正想讨论的,不是"首页怎么做成配置化",而是首页什么时候开始从一个页面,慢慢变成一个要负责承接跳转、安排入口、重新组织内容的总入口。
1. 首页一旦成了很多跳转共同的落脚点,角色就已经变了
项目继续长一段时间以后,首页很容易悄悄长出一种新角色:
- 不是用户点一下就进来的首页
- 而是别的流程收不住时,要先退回来的那个位置
这件事比"tab 能不能动态"更值得注意。
因为它说明系统已经默认把首页当成:
- 一个稳定基点
- 一个统一入口
- 一个可以继续展开下一步跳转的地方
这类角色一旦成立,首页真正开始关心的就不再只是页面怎么画、tab 怎么排,而会变成:
- 哪些跳转应该先回首页
- 哪些目标页可以直接进
- 哪些入口需要先做登录校验
- 哪些内容得先回到首页这一层再继续往下分
也就是说,首页最先变复杂的地方,往往不是页面显示,而是很多流程最后都要借它走一遍。
2. 真正把首页拖复杂的,不是配置字段本身,而是不同地方进来后要不要当成同一个首页处理
首页一旦开始接很多不同入口,页面本身就会变得没那么"固定"。
项目里 fromTaskPage 这种开关很小,但代表性特别强。它说明同样都是回首页,系统还得继续区分:
- 你是正常点进来的
- 还是从任务页跳回来的
- 是从资源位带进来的
- 还是从 Deep Link 直达后又退回来的
- 甚至是不是从 WebView、小程序退出以后再回到首页这套逻辑里
一旦这些路径同时存在,"首页默认长什么样"这个问题就已经不够用了。
更接近真实的问题反而是:
- 这次默认应该落在哪一页
- 之前的状态还要不要保
- 这次回来的用户,应该看到原来的首页,还是改过组织后的首页
所以首页复杂度真正开始长出来,不是因为页面本身变花了,而是因为同样叫首页,从不同地方进来的其实已经不是同一种体验。
3. 配置化后面最重的,不是 tab 显示,而是老页面、旧状态和默认先到哪一页怎么一起调整
不少人会先把首页配置化理解成:
- tab 从写死改成接口返回
- 某些频道能开关
- 用户 A 和用户 B 看到的顺序不一样
这些当然都算配置化,但还不是最重的地方。
真正把事情变复杂的,是首页调整以后,老页面和旧状态还在不在。
DynamicTabController 这段逻辑就很能说明问题:
dart
final Map<String, Widget> existingViews = {};
final oldTabs = _previousTabs.toList();
for (int i = 0; i < oldTabs.length && i < tabViews.length; i++) {
existingViews[_normalizeTabName(oldTabs[i])] = tabViews[i];
}
for (String tab in tabs) {
final normalizedTab = _normalizeTabName(tab);
if (existingViews.containsKey(normalizedTab)) {
tabViews.add(existingViews[normalizedTab]!);
} else {
...
}
}
这段代码真正在回答的,不是"tab 对不对",而是:
- 老页面要不要继续沿用
- 老状态要不要保
- 页面切回来时该不该刷新
而 DynamicPageConfig 这一层又把另一个问题抬了出来。
它已经不只是在说"显示不显示",而是在决定:
- 谁能看到什么
- 某个页面什么时候出现
- 默认先落在哪个 tab
- 从不同入口回来时,首页要不要换一种页面安排
比如这段默认配置:
dart
static const List<TabConfig> defaultTabs = [
TabConfig(
name: '论坛',
key: 'forum',
requireAuth: false,
requireConfig: false,
order: 0,
),
TabConfig(
name: '关注',
key: 'follow',
requireAuth: true,
requireConfig: false,
order: 1,
),
...
];
static const String defaultSelectedTab = 'discover';
static bool fromTaskPage = false;
这些字段一旦组合在一起,首页真正要处理的就不再只是"显示哪些 tab",而是:
- 入口筛选
- 默认先到哪一页
- 登录态差异
- 配置差异
- 老状态怎么保留下来
所以首页配置化真正变重的地方,常常不是 UI,而是页面重新调整以后,旧东西和新入口怎么一起处理。
4. 当资源位、H5、小程序都接进来以后,首页已经在替整套页面关系决定去向了
首页会继续变重,还有一个经常被低估的原因:
它后面接的内容类型越来越杂了。
项目里这几层一接进来,首页就已经很难只被当成一个普通页面看了:
- 资源位服务会主动预加载
- 小程序控制器会在启动阶段预热默认小程序
- 本地
LocalServer会被拉起来组织小程序内容 - 远端配置
game_suport_show还会影响小程序页要不要显示
这说明首页最后要处理的,已经不只是几个原生模块,而是一整套内容能力:
- 原生页
- H5 页面
- 小程序页
- 资源位驱动内容
这时候,很多看起来像"首页问题"的事情,最后都会被拉成另一类问题:
- 内容该落在哪一页
- 不同内容进来以后首页怎么接
- 跳转规则怎么统一
- 初始化时机怎么安排
走到这里,首页其实已经在替整套页面关系决定去向了。
所以我现在再看"配置化首页"这类需求,已经不会先去想"tab 怎么写",而是:
- 这个首页最后要处理多少种内容
- 它是不是已经成了很多跳转共同的落脚点
- 它是不是已经在替整套页面关系决定入口怎么走
只要这些问题开始出现,首页就已经不只是首页了。