普通视图

发现新文章,点击刷新页面。
今天 — 2026年3月28日首页

为什么很多复杂跳转,最后都得先回首页?

2026年3月28日 18:15

首页真正开始变重,往往不是 tab 能不能动态,而是它一边要处理远端配置,一边要处理登录态差异和多入口跳转。用户从不同地方进来,默认先到哪一页、旧状态要不要保、资源位和不同内容往哪儿走,都会一起变。走到这一步,首页处理的就不只是页面显示,而是整套跳转和页面安排。

我这次重新看首页相关代码,最先让我停住的,不是动态 tab,也不是某个配置字段,而是一段跳转逻辑。

很多入口并不是直接去目标页,而是要先退回首页,再继续往下走:

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 这段逻辑就很能说明问题:

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
  • 从不同入口回来时,首页要不要换一种页面安排

比如这段默认配置:

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 怎么写”,而是:

  • 这个首页最后要处理多少种内容
  • 它是不是已经成了很多跳转共同的落脚点
  • 它是不是已经在替整套页面关系决定入口怎么走

只要这些问题开始出现,首页就已经不只是首页了。

❌
❌