ReactJS中如何共享包含setState的函数?最佳方案咨询
React中带setState的跨页面认证函数共享方案
一、Util.js共享函数的局限与实现
如果用单独的Util.js封装认证逻辑,确实需要将setToken、setName、setExpire这些状态更新函数作为参数传入工具函数——因为工具文件本身不具备React组件的状态上下文,无法直接调用组件内的setState。示例如下:
// Util.js import axios from 'axios'; import jwt_decode from 'jwt-decode'; export const setupAxiosJWT = (setToken, setName, setExpire, host, currentExpire) => { const axiosJWT = axios.create(); axiosJWT.interceptors.request.use(async (config) => { const currentDate = new Date(); if (currentExpire * 1000 < currentDate.getTime()) { const response = await axios.get(`http://${host}/token`); config.headers.Authorization = `Bearer ${response.data.accessToken}`; setToken(response.data.accessToken); const decoded = jwt_decode(response.data.accessToken); setName(decoded.username); setExpire(decoded.exp); } return config; }, (error) => { return Promise.reject(error); }); return axiosJWT; };
页面中调用方式:
// PageA.js import { setupAxiosJWT } from './Util'; const PageA = () => { const [token, setToken] = useState(''); const [name, setName] = useState(''); const [expire, setExpire] = useState(0); const host = 'your-host-address'; const axiosJWT = setupAxiosJWT(setToken, setName, setExpire, host, expire); // 后续用axiosJWT发起请求 return <div>...</div>; };
但这种方式存在明显问题:每个页面调用setupAxiosJWT都会创建新的axios实例并注册拦截器,重复创建会带来性能开销,甚至可能引发拦截逻辑冲突。
二、更优方案:全局状态管理+自定义Hook
1. 用Context统一维护认证状态
将用户认证相关的状态(token、name、expire)提升到全局Context,避免每个页面单独维护重复状态:
// AuthContext.js import { createContext, useContext, useState, useEffect } from 'react'; import axios from 'axios'; import jwt_decode from 'jwt-decode'; const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const [token, setToken] = useState(localStorage.getItem('token') || ''); const [name, setName] = useState(''); const [expire, setExpire] = useState(0); const host = 'your-host-address'; // 初始化时解码已有token useEffect(() => { if (token) { const decoded = jwt_decode(token); setName(decoded.username); setExpire(decoded.exp); } }, [token]); // 封装刷新token的逻辑 const refreshToken = async () => { try { const response = await axios.get(`http://${host}/token`); const newToken = response.data.accessToken; setToken(newToken); localStorage.setItem('token', newToken); const decoded = jwt_decode(newToken); setName(decoded.username); setExpire(decoded.exp); return newToken; } catch (error) { // 刷新失败跳转登录页 window.location.href = '/'; throw error; } }; return ( <AuthContext.Provider value={{ token, name, expire, refreshToken }}> {children} </AuthContext.Provider> ); }; export const useAuth = () => useContext(AuthContext);
在App组件中包裹全局Provider:
// App.js import { AuthProvider } from './AuthContext'; function App() { return ( <AuthProvider> <BrowserRouter> <Switch> <Route exact path="/"> <Login/> </Route> <Route path="/pageA"> <Navbar/> <Page content={PageA}/> </Route> <Route path="/pageB"> <Navbar/> <Page content={PageB}/> </Route> ... </Switch> </BrowserRouter> </AuthProvider> ); }
2. 封装全局axios实例+自定义Hook绑定拦截器
将axiosJWT作为全局实例只创建一次,通过自定义Hook绑定拦截器(确保能访问Context中的状态):
// api.js import axios from 'axios'; import { useAuth } from './AuthContext'; // 创建全局axios实例,只初始化一次 const axiosJWT = axios.create(); export const useAxiosJWT = () => { const { token, expire, refreshToken } = useAuth(); useEffect(() => { // 注册请求拦截器 const requestInterceptor = axiosJWT.interceptors.request.use(async (config) => { const currentDate = new Date(); if (expire * 1000 < currentDate.getTime()) { const newToken = await refreshToken(); config.headers.Authorization = `Bearer ${newToken}`; } else { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => { return Promise.reject(error); }); // 组件卸载时移除拦截器,避免内存泄漏 return () => { axiosJWT.interceptors.request.eject(requestInterceptor); }; }, [token, expire, refreshToken]); return axiosJWT; };
页面中使用方式:
// PageA.js import { useAxiosJWT } from './api'; const PageA = () => { const axiosJWT = useAxiosJWT(); const fetchData = async () => { try { const response = await axiosJWT.get('/your-api-path'); // 处理返回数据 } catch (error) { // 处理请求错误 } }; return <div>...</div>; };
三、axiosJWT的创建位置结论
axiosJWT应该只创建一次,放在单独的api.js或Util.js中,而非每个页面都创建。原因如下:
- 重复创建实例会导致多次注册拦截器,可能引发逻辑冲突(比如重复刷新token)
- 全局单一实例可以统一配置请求头、超时时间等,维护更高效
- 配合自定义Hook和Context,既能共享拦截逻辑,又能正确访问全局状态
内容的提问来源于stack exchange,提问作者Airborne STD
相关产品推荐
相关产品推荐

