React中useContext的TypeScript类型配置及刷新状态异常问题排查
问题分析与修复方案
核心问题
- 刷新时重定向过早触发:页面刷新后,
UserContextProvider的useEffect会在组件挂载后才读取localStorage,但Dashboard会先以初始user: null渲染,直接触发if(!user)的登录页重定向,等localStorage读取完成更新状态时,重定向已经完成了。 - 初始值设为
{}的隐患:未登录时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
相关产品推荐
相关产品推荐

