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

React中useContext的TypeScript类型配置及刷新状态异常问题排查

问题分析与修复方案

核心问题

  1. 刷新时重定向过早触发:页面刷新后,UserContextProvider的useEffect会在组件挂载后才读取localStorage,但Dashboard会先以初始user: null渲染,直接触发if(!user)的登录页重定向,等localStorage读取完成更新状态时,重定向已经完成了。
  2. 初始值设为{}的隐患:未登录时user是空对象,!user判断为false,导致Dashboard跳过路由保护,未登录用户也能访问。

具体修复步骤

1. 在Context中添加加载状态,避免过早渲染子组件

修改UserContext.tsx,添加加载状态,确保localStorage读取完成后再渲染页面内容:

import {createContext, useEffect, useState} from 'react'
import { UserProps } from './types'

// 更新Context类型,加入加载状态字段
export const UserContext  = createContext<UserProps & {isLoading: boolean}>({} as UserProps & {isLoading: boolean})

type UserContextProviderProps = {
    children: React.ReactNode
}

const UserContextProvider = ({children}: UserContextProviderProps) => {
    // 明确用户数据类型,避免泛用{}
    const [user, setUser] = useState<{name?: string, token?: string} | null>(null)
    const [isLoading, setIsLoading] = useState(true)

    useEffect(() => {
        const loggedInUser = localStorage.getItem("user");
        if (loggedInUser) {
            try {
                const parsedUser = JSON.parse(loggedInUser);
                // 验证用户数据有效性(比如是否存在token)
                if (parsedUser?.token) {
                    setUser(parsedUser);
                } else {
                    localStorage.removeItem("user"); // 清除无效数据
                }
            } catch (e) {
                localStorage.removeItem("user"); // 解析失败时清除错误数据
            }
        }
        setIsLoading(false); // 标记加载完成
    }, [])

    return (
        <UserContext.Provider value={{user, setUser, isLoading}}>
            {/* 加载完成前不渲染子组件,避免无效重定向 */}
            {!isLoading && children}
        </UserContext.Provider>
    )
}

export default UserContextProvider

2. 更新TypeScript类型定义

修改types.ts,让类型更准确:

export type User = {
    name?: string,
    token?: string
} | null

export type UserProps = {
    user: User,
    setUser: React.Dispatch<React.SetStateAction<User>>,
    isLoading: boolean // 新增加载状态字段
} 

3. 调整Dashboard组件,处理加载状态

在Dashboard中先判断加载状态,加载期间不执行重定向,同时用token判断登录状态更可靠:

import React, {useContext, useState} from 'react'
import { Navigate } from "react-router-dom";
import { UserContext } from '../UserContext'
import { UserProps } from '../types'

const Dashboard = () => {
    const {setUser, user, isLoading}: UserProps = useContext(UserContext)
    const [redirect,setRedirect] = useState<boolean>(false);

    const handleLogout = () => {
      setUser(null)
      localStorage.removeItem("user")
      setRedirect(true)
    }

    if(redirect){
      return <Navigate to={'/'} />
    }

    // 加载期间显示提示,不执行重定向
    if(isLoading){
      return <div>加载中...</div>
    }

    // 用token判断是否登录,比单纯判断user是否为null更可靠
    if(!user || !user.token){
      return <Navigate to={'/login'}/> 
    }

    return (
      <div className='flex flex-col'>
        <p>DashBoard</p>
        <p>{user?.name}</p>
        <button className='border border-black w-[100px]' onClick={handleLogout} >Log Out</button>
      </div>
    )
}

export default Dashboard

4. 优化Login组件的跳转逻辑

用useNavigate代替状态跳转,更符合React Router的最佳实践:

import { useState, useContext } from 'react'
import { useNavigate } from 'react-router-dom'
import axios from 'axios'
import { UserContext } from './UserContext'
import { UserProps } from './types'

const LoginPage = () => {
    const [email, setEmail] = useState<string>('')
    const [password, setPassword] = useState<string>('')
    const {setUser}: UserProps = useContext(UserContext)
    const navigate = useNavigate()

    const handleLogin = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
        try {
            const {data} = await axios.post('/auth/login', {email,password})
            setUser(data)
            localStorage.setItem('user', JSON.stringify(data))
            navigate('/dashboard') // 直接跳转,无需状态触发
        } catch (error) {
            console.error('登录失败', error)
        } 
    }

    return (
        <form onSubmit={handleLogin}>
            <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="邮箱" />
            <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="密码" />
            <button type="submit">登录</button>
        </form>
    )
}

export default LoginPage

内容的提问来源于stack exchange,提问作者gerard

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 00:54:54