从useAuth解构onLogout遇Null错误,ProtectedRoute使用正常求助
问题原因分析
这个错误的核心是Context的作用域问题:你在App组件的根级别直接调用了useAuth(),但此时AuthProvider还没有完成渲染,所以useContext(AuthContext)拿到的是你创建Context时传入的默认值null,自然无法解构onLogout属性。
而ProtectedRoute之所以能正常工作,是因为它是作为AuthProvider的children被渲染的——也就是说,当ProtectedRoute执行useAuth()时,它已经处于AuthContext.Provider的作用域范围内,能正确获取到Provider提供的value对象。
解决方案
我们需要把所有依赖AuthContext的代码,放到AuthProvider的作用域内部。这里有两种清晰的修改方式:
方式一:拆分组件,将依赖Context的逻辑移到Provider内部
把App中用到onLogout的部分拆成一个独立子组件,比如AppContent,然后让这个子组件在AuthProvider内部渲染:
import React, { createContext, useContext } from "react"; import { Navigate, Route, Routes, useNavigate } from "react-router-dom"; import { useDispatch } from "react-redux"; import { logout, userLogin, userRegister } from "./features/cv"; import { cookieCutter } from "./utils/cookie"; const AuthContext = createContext(null); const AuthProvider = ({ children }) => { const token = cookieCutter("token"); const dispatch = useDispatch(); const navigate = useNavigate(); const handleLogin = (data) => { dispatch(userLogin(data)); navigate("/table"); }; const handleRegister = (data) => { dispatch(userRegister(data)); navigate("/table"); }; const handleLogout = () => { dispatch(logout()); navigate("/login"); }; const value = { token, onLogin: handleLogin, onRegister: handleRegister, onLogout: handleLogout, }; return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; }; const useAuth = () => { return useContext(AuthContext); }; // 单独定义ProtectedRoute,避免嵌套在App内部 const ProtectedRoute = ({ children }) => { const { token } = useAuth(); if (!token) { return <Navigate to="/login" replace />; } return children; }; // 拆分出AppContent,放在AuthProvider内部 const AppContent = () => { const userString = JSON.parse(cookieCutter("user") ?? "{}"); const { onLogout } = useAuth(); // 这里可以渲染你的导航栏、调用onLogout的按钮等 return ( <div> {/* 示例:添加退出登录按钮 */} <button onClick={onLogout}>退出登录</button> <Routes> <Route path="/" element={ <ProtectedRoute> <Create /> </ProtectedRoute> } /> <Route path="/table" element={ <ProtectedRoute> <Table /> </ProtectedRoute> } /> <Route path="/preview" element={ <ProtectedRoute> <Preview /> </ProtectedRoute> } /> <Route path="/login" element={<Login />} /> <Route path="/register" element={<Register />} /> </Routes> </div> ); }; function App() { return ( <AuthProvider> <AppContent /> </AuthProvider> ) }
方式二:将useAuth的调用移到Provider包裹的JSX内部
如果你不想拆分组件,也可以把依赖onLogout的逻辑直接写到AuthProvider的children里:
function App() { // 不要在这里调用useAuth()! return ( <AuthProvider> {/* 把需要useAuth的逻辑移到这里 */} {() => { const userString = JSON.parse(cookieCutter("user") ?? "{}"); const { onLogout } = useAuth(); return ( <div> <button onClick={onLogout}>退出登录</button> <Routes> {/* 路由定义和之前一致 */} </Routes> </div> ) }} </AuthProvider> ) }
不过这种方式不如拆分组件清晰,更推荐第一种方案。
额外建议
另外,建议把ProtectedRoute从App组件内部移出来单独定义——嵌套定义组件会导致每次App渲染时都创建一个新的ProtectedRoute组件,可能引发不必要的重渲染,也不利于代码复用。
内容的提问来源于stack exchange,提问作者sansalgo
相关产品推荐
相关产品推荐

