You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NextJS中useLayoutEffect不等待异步函数及状态更新疑问

页面刷新时的登录校验问题及状态更新疑问

场景代码

_app.tsx 中的登录校验逻辑

const { authenticated, setAuthenticated } = useContext(AuthContext)

const fetchRefresh = async () => {
  const status = await AuthStore.checkAuth()
  if (status == 200) {
    setAuthenticated && setAuthenticated(true)
  } else {
    setAuthenticated && setAuthenticated(false)
  }
  return status
}

useLayoutEffect(() => {
  if (localStorage.getItem("access_token")) {
    fetchRefresh()
  }
}, [])

Layout 组件中的状态校验与重定向

export const withLayout = <T extends Record<string, unknown> & IAuthContext>(Component: FunctionComponent<T>) => {
    return function withLayoutComponent(props: T): JSX.Element {
        const { authenticated } = useContext(AuthContext)
        const router = useRouter()

        useEffect(() => {
            if (!authenticated) router.push("/auth/login")
        }, [authenticated])

        return (
            <Layout>
                <Component {...props} />
            </Layout>
        );
    };
};

登录页也存在对应的useEffect逻辑:若用户已登录则重定向至"/"页面。


问题1:页面刷新时的闪烁与多次重定向

控制台执行顺序如下:

  1. useLayoutEffect(_app.tsx)
  2. use effect (index.tsx)
  3. complete the fetchRefresh function

问题根源在于登录校验接口是异步的:页面刷新时初始authenticated为false,Layout会先触发重定向到登录页;等接口返回确认用户已登录后,登录页的useEffect又会重定向回首页,最终导致页面闪烁、多次跳转。

解决方案

1. 在AuthContext中新增加载状态

给上下文添加isLoading状态,标记登录校验请求的完成状态:

// AuthContext 定义示例
const AuthContext = createContext<{
  authenticated: boolean;
  setAuthenticated: (val: boolean) => void;
  isLoading: boolean;
  setIsLoading: (val: boolean) => void;
}>({
  authenticated: false,
  setAuthenticated: () => {},
  isLoading: true,
  setIsLoading: () => {},
});

2. 修改_app.tsx的校验逻辑

在请求开始和结束时更新isLoading:

const { authenticated, setAuthenticated, isLoading, setIsLoading } = useContext(AuthContext)

const fetchRefresh = async () => {
  setIsLoading(true)
  try {
    const status = await AuthStore.checkAuth()
    setAuthenticated(status === 200)
  } finally {
    setIsLoading(false)
  }
}

useLayoutEffect(() => {
  if (localStorage.getItem("access_token")) {
    fetchRefresh()
  } else {
    setIsLoading(false)
  }
}, [])

3. 调整Layout的重定向逻辑

仅当加载完成且用户未登录时,才执行重定向;加载过程中显示占位内容避免闪烁:

export const withLayout = <T extends Record<string, unknown> & IAuthContext>(Component: FunctionComponent<T>) => {
    return function withLayoutComponent(props: T): JSX.Element {
        const { authenticated, isLoading } = useContext(AuthContext)
        const router = useRouter()

        useEffect(() => {
            if (!isLoading && !authenticated) {
                router.push("/auth/login")
            }
        }, [authenticated, isLoading])

        if (isLoading) {
            return <div>加载中...</div>
        }

        return (
            <Layout>
                <Component {...props} />
            </Layout>
        );
    };
};

4. 同步修改登录页逻辑

同样判断isLoading,避免提前触发重定向:

// 登录页示例
const { authenticated, isLoading } = useContext(AuthContext)
const router = useRouter()

useEffect(() => {
    if (!isLoading && authenticated) {
        router.push("/")
    }
}, [authenticated, isLoading])

if (isLoading) {
    return <div>加载中...</div>
}

// 登录页渲染代码

问题2:为何setAuthenticated(true)后打印authenticated仍为false?

React的状态更新是异步批量更新机制:调用setAuthenticated后,当前作用域中的authenticated变量是组件上一次渲染的快照,不会立即更新,只有等组件重新渲染后,才能拿到最新的状态值。

如果需要在状态更新后执行逻辑,可通过两种方式实现:

  1. 使用useEffect监听authenticated变化:
useEffect(() => {
  console.log(authenticated) // 状态更新后会触发此处
}, [authenticated])
  1. 在异步函数中直接使用接口返回的结果:
const fetchRefresh = async () => {
  const status = await AuthStore.checkAuth()
  const isAuth = status === 200
  setAuthenticated(isAuth)
  console.log(isAuth) // 直接使用该变量而非authenticated
  return status
}

内容的提问来源于stack exchange,提问作者Михаил Васильев

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 16:35:49