如何使用 React Router V4 从 axios 拦截器重定向?
Posted
技术标签:
【中文标题】如何使用 React Router V4 从 axios 拦截器重定向?【英文标题】:How to redirect from axios interceptor with react Router V4? 【发布时间】:2017-08-29 20:20:39 【问题描述】:我想在收到 403 错误时在 axios 拦截器中进行重定向。但是如何访问 React 组件之外的历史记录?
在Navigating Programatically in React-Router v4 中,它在 React 组件的上下文中,但这里我在 axios 上下文中尝试
axios.interceptors.response.use(function (response)
// Do something with response data
return response;
, function (error)
// Do something with response error
if(error.response.status === 403) console.log("Redirection needed !");
// Trow errr again (may be need for some other catch)
return Promise.reject(error);
);
【问题讨论】:
试试这个代码:import browserHistory from 'react-router';
然后browserHistory.push("/path");
用import browserHistory from 'react-router-dom';
和browserHistory.push("/path")
尝试过,但它不起作用,这是V3 方式,不是吗?
是的,不幸的是,这似乎不适用于 V4 路由器...
react-router-v6 ***.com/q/69953377/14665310问题的链接
【参考方案1】:
我通过从组件树外部访问我的 Redux Store 并从注销按钮发送相同的操作来解决这个问题,因为我的拦截器是在一个单独的文件中创建并在加载任何组件之前加载的。
所以,基本上,我做了以下事情:
index.js
文件:
//....lots of imports ommited for brevity
import createStore, applyMiddleware from 'redux';
import reduxThunk from 'redux-thunk';
import reducers from './reducers';
import UNAUTH_USER from './actions/types'; //this is just a constants file for action types.
const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
const store = createStoreWithMiddleware(reducers);
//Here is the guy where I set up the interceptors!
NetworkService.setupInterceptors(store);
//lots of code ommited again...
//Please pay attention to the "RequireAuth" below, we'll talk about it later
ReactDOM.render(
<Provider store=store>
<BrowserRouter>
<div>
<Header />
<main className="plan-container">
<Switch>
<Route exact path="/" component=Landing />
<Route exact path="/login" component=Login />
<Route exact path="/signup" component=Signup />
<Route exact path="/calendar" component=RequireAuth(Calendar) />
<Route exact path="/profile" component=RequireAuth(Profile) />
</Switch>
</main>
</div>
</BrowserRouter>
</Provider>
, document.querySelector('.main-container'));
在network-service.js
文件中:
import axios from 'axios';
import UNAUTH_USER from '../actions/types';
export default
setupInterceptors: (store) =>
// Add a response interceptor
axios.interceptors.response.use(function (response)
return response;
, function (error)
//catches if the session ended!
if ( error.response.data.token.KEY == 'ERR_EXPIRED_TOKEN')
console.log("EXPIRED TOKEN!");
localStorage.clear();
store.dispatch( type: UNAUTH_USER );
return Promise.reject(error);
);
;
最后但并非最不重要的一点是,我有一个 HOC(高阶组件),我将受保护的组件包装在其中,当会话结束时我会在其中执行实际重定向。这样,当我触发动作类型 UNAUTH_USER 时,它会将我的 session
减速器的 isLogged
属性设置为 false
,因此该组件会随时收到通知并为我执行重定向。
require-auth.js
组件的文件:
import React, Component from 'react';
import connect from 'react-redux';
export default function(ComposedComponent)
class RequireAuth extends Component
componentWillMount()
if(!this.props.session.isLogged)
this.props.history.push('/login');
;
componentWillUpdate(nextProps)
if(!nextProps.session.isLogged)
this.props.history.push('/login');
;
render()
return <ComposedComponent ...this.props />
function mapStateToProps(state)
return session: state.session ;
return connect(mapStateToProps)(RequireAuth);
希望有帮助!
【讨论】:
首先,这是 HOC 的绝佳解决方案!但是,您不想检查componentDidUpdate
中的下一个更新的会话道具,而不是componentWillUpdate
?
@w3bshark 当我在componentWillUpdate
或componentWillMount
中检查它时,我实际上是在避免在会话丢失时重新渲染组件。如果我在componentDidUpdate
中这样做,它会首先被渲染,然后检查会话是否应该存在,在我看来,这是对安全性的破坏和错误的设计。
我无法理解您的解决方案。拦截器不会添加到您要导出的任何 axios 实例中,那么如何发出 api 请求以使用所述拦截器?我想你没有暴露window.axios
。看来您需要以某种方式导出用拦截器装饰的 axios 实例,但我看不出那会在哪里
嗨@MaciejGurban。据我所知,axios 在导入时就像一个单例,所以只要你在某个时候调用拦截器,它就会在库的同一个实例中应用于整个应用程序。
我没有使用 redux,而是使用 React Context 来管理应用程序状态。我不确定是否有办法从 React 外部调用用户上下文。任何指示/建议?【参考方案2】:
我通过从 history
(https://github.com/ReactTraining/history) 包创建浏览器历史记录并将其传递给拦截器函数,然后从中调用 .push()
方法来解决此任务。
主文件代码(部分):
// app.js
import createBrowserHistory from 'history';
import httpService from './api_client/interceptors';
...
const history = createBrowserHistory();
httpService.setupInterceptors(store, history);
拦截器配置:
import axios from 'axios';
export default
setupInterceptors: (store, history) =>
axios.interceptors.response.use(response =>
return response;
, error =>
if (error.response.status === 401)
store.dispatch(logoutUser());
if (error.response.status === 404)
history.push('/not-found');
return Promise.reject(error);
);
,
;
此外,您应该使用来自react-router
(https://github.com/ReactTraining/react-router) 的Router
,并传递与history
参数相同的历史对象。
// app.js
...
ReactDOM.render(
<Provider store=store>
<Router history=history>
...
</Router>
</Provider>
, document.getElementById('#root'))
【讨论】:
在这种情况下,您用来进行 API 调用的代码是什么样的,即您导入什么来发出请求? @MaciejGurban 我简单地导入了 axios 库并照常使用get
、post
等
我明白了,那么您将如何测试这些拦截器?
谢谢你救了我【参考方案3】:
这似乎对我有用
function (error)
var accessDenied = error.toString().indexOf("401");
if (accessDenied !== -1)
console.log('ACCESS DENIED')
return window.location.href = '/accessdenied'
);
【讨论】:
这将完全重新加载选项卡并从一开始就开始反应,而不是操纵window.history
【参考方案4】:
这很好用。
window.location.href = `$process.env.REACT_APP_BASE_HREF/login`;
【讨论】:
【参考方案5】:我发现的最佳解决方案是在我的主要 React 组件中定义 axios.interceptors 并使用 that
来处理错误:
(以及来自路由器 V4 的 withRouter
)
import withRouter from 'react-router-dom';
class Homepage extends Component
static propTypes =
history: PropTypes.object.isRequired
constructor(props)
super(props);
let that = this;
axios.interceptors.response.use(function (response)
// Do something with response data
return response;
, function (error)
// Do something with response error
if(error.response.status === 403) that.handle403()
// Trow errr again (may be need for some other catch)
return Promise.reject(error);
);
handle403()
this.props.history.push('/login');
【讨论】:
这个依赖于组件初始化顺序,如果在安装拦截器之前有组件发出axios请求,将不起作用。【参考方案6】:刚刚意识到问题是针对反应路由器 v4 的,我已经写了我在 v5 中使用的答案。
我通过将 useHistory()
从 <Router>
内部传递给 axios 拦截器解决了这个问题。
App.js:
// app.js
function App()
return (
<Router>
<InjectAxiosInterceptors />
<Route ... />
<Route ... />
</Router>
)
InjectAxiosInterceptors.js:
import useEffect from "react"
import useHistory from "react-router-dom"
import setupInterceptors from "./plugins/http"
function InjectAxiosInterceptors ()
const history = useHistory()
useEffect(() =>
console.log('this effect is called once')
setupInterceptors(history)
, [history])
// not rendering anything
return null
插件/http.js:
import axios from "axios";
const http = axios.create(
baseURL: 'https://url'
)
/**
* @param import('history').History history - from useHistory() hook
*/
export const setupInterceptors = history =>
http.interceptors.response.use(res =>
// success
return res
, err =>
const status = err.response
if (status === 401)
// here we have access of the useHistory() from current Router
history.push('/login')
return Promise.reject(err)
)
export default http
【讨论】:
如何在组件内部使用拦截器。【参考方案7】:这是对我有用的已接受答案的修改版本。
使用 BrowserRouter 将 App 组件包装在 index.js 中,否则 useHistory() 挂钩将不起作用。
import React from 'react';
...
import BrowserRouter from "react-router-dom";
ReactDOM.render(
<Provider store=store>
<BrowserRouter><App /></BrowserRouter>
</Provider>,
document.getElementById('root')
);
创建一个单独的文件来实例化自定义 axios 实例
import axios from 'axios';
let headers = ;
const baseURL = "http://localhost:8080"
const jwtToken = localStorage.getItem("Authorization");
if (jwtToken)
headers.Authorization = 'Bearer ' + jwtToken;
const axiosInstance = axios.create(
baseURL: baseURL,
headers,
);
export default axiosInstance;
使用之前创建的自定义 axios 实例的拦截器方法创建另一个文件。
import axiosInstance from "./ServerAxios";
import useHistory from "react-router-dom";
const baseURL = "http://localhost:8080"
const SetupInterceptors = () =>
let history = useHistory();
axiosInstance.interceptors.response.use(function (response)
return response;
, function (error)
var status = error.response.status;
var resBaseURL = error.response.config.baseURL;
if (resBaseURL === baseURL && status === 403)
localStorage.removeItem("Authorization");
history.push("/login");
return Promise.reject(error);
);
export default SetupInterceptors;
然后导入并调用App.js文件中的setup方法
...
import createBrowserHistory from 'history';
import SetupInterceptors from './middleware/NetworkService';
const App = () =>
const history = createBrowserHistory();
SetupInterceptors(history);
...
然后每当你需要使用自定义的 axios 实例时,导入实例化的文件并使用它。
import ServerAxios from "../middleware/ServerAxios";
ServerAxios.post(......);
【讨论】:
【参考方案8】:接受的答案并不能解决我的问题。在 axios 和拦截器周围的票没有触发之后,我发现 axios 不支持像上面描述的那样全局装饰拦截器。对于未来的读者,请记住,axios 已将此global interceptor
标记为功能。所以也许我们将来会得到它。参考:https://github.com/axios/axios/issues/993。
我确实有一个用于所有 api 调用的 axios 实例,所以我解决了在其中定义拦截器的问题。
【讨论】:
【参考方案9】:我正在使用 react-router-dom,它具有“历史”道具,可用于过渡到新路线
history.push('/newRoute')
【讨论】:
以上是关于如何使用 React Router V4 从 axios 拦截器重定向?的主要内容,如果未能解决你的问题,请参考以下文章
从 v3 迁移到 v4 react-router 时出现错误
react-router 从 v3 版本升到 v4 版本,升级小记