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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 12:35:25