PAYATHON 2026

Expo Router 在 useEffect 中跳转时提示 Root Layout 未挂载怎么办?

数据小许

结论

先让 Root Layout 渲染 <Slot /> 或 <Stack /> 等导航组件,再执行跳转。把 router.replace() 放进 useEffect 并不能保证导航已经就绪。

登录拦截可以这样处理:根布局负责提供鉴权状态并渲染导航,未登录时的重定向放在受保护路由组的嵌套布局中。Expo 的鉴权重定向指南也采用了这种结构。

为什么 useEffect 中仍然报错?

日志显示,app/_layout.tsx 在 isAuth 为假时调用了:

route.replace("/(auth)/sign-in");

此时路由系统报告 Root Layout 尚未挂载。useEffect 执行时,对应组件已经完成一次提交;但如果那次渲染没有输出导航组件,仍然没有可用于跳转的导航结构。

例如,下面的写法在鉴权加载期间不渲染 <Slot />,但仍可能尝试跳转:

useEffect(() => {
  if (!isAuth) {
    router.replace('/(auth)/sign-in');
  }
}, [isAuth]);

if (isLoading) {
  return null;
}

return <Slot />;

这是一种可能的触发方式。由于没有完整的布局代码,无法断定项目中是否存在这个分支。还需要检查包裹 <Slot /> 的 Provider 是否在初始化时返回了 null 或加载页面。

如何调整布局和鉴权逻辑?

以下是建议的目录结构,并非原始项目的实际目录:

app/
├── _layout.tsx
├── (auth)/
│   └── sign-in.tsx
└── (app)/
    ├── _layout.tsx
    └── index.tsx

登录页放在 (auth) 组,需要登录的页面放在 (app) 组。

示例假设项目中有 auth.tsx,提供 AuthProvider 和 useAuth(),后者返回 isAuth 与 isLoading。这些是示例中的自定义接口,需要替换为项目已有的实现;isLoading 表示登录状态是否仍在恢复或验证中。

1. 根布局渲染导航入口

app/_layout.tsx:

import { Slot } from 'expo-router';
import { AuthProvider } from '../auth';

export default function RootLayout() {
  return (
    <AuthProvider>
      <Slot />
    </AuthProvider>
  );
}

AuthProvider 应通过 Context 暴露加载状态,同时继续渲染 children。删除根布局中原来负责登录拦截的 useEffect,并检查 Provider 内是否还有启动时的导航调用。

2. 在受保护路由组中处理加载和重定向

app/(app)/_layout.tsx:

import { Redirect, Stack } from 'expo-router';
import { Text } from 'react-native';
import { useAuth } from '../../auth';

export default function ProtectedLayout() {
  const { isAuth, isLoading } = useAuth();

  if (isLoading) {
    return <Text>正在检查登录状态…</Text>;
  }

  if (!isAuth) {
    return <Redirect href="/(auth)/sign-in" />;
  }

  return <Stack />;
}

这里先等待鉴权完成,再根据登录状态决定重定向还是渲染页面。登录页位于受保护组之外,未登录用户才能正常访问。这个结构对应官方指南中的嵌套布局鉴权示例。

还需要检查什么?

  • 确认 /(auth)/sign-in 对应的页面确实存在。当前材料没有实际目录结构,无法核对路径。
  • 核对日志中的 route 如何定义。如果它来自 const route = useRouter(),变量名可以保留;如果来自其他 API,则需要检查调用对象。
  • 不要把尚未读取到登录状态直接当成未登录。异步恢复会话时,应单独维护加载状态。
  • 不要用固定的 setTimeout 延迟掩盖挂载问题;等待一定时间不能保证导航已经就绪。

如果修改后仍然报错,需要结合完整的 app/_layout.tsx、Provider、目录结构及 Expo SDK、expo-router 版本继续定位。SDK 53 引入了 Protected routes;使用支持该功能的版本时,也可以参考官方鉴权指南,通过 Stack.Protected 管理访问条件。

备注:内容仅供参考。