如何使用 React Router V4 从 axios 拦截器重定向?
How to redirect from axios interceptor with react Router V4?
我想在收到 403 错误时在 axios 拦截器中进行重定向。但是我如何访问 React 组件之外的历史记录?
在 中,它在 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);
});
我找到的最佳解决方案是在我的主要 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');
}
我通过从组件树外部访问我的 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 时,它会将我的 isLogged
属性 在我的 session
reducer 设置为 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);
}
希望对您有所帮助!
我正在使用 react-router-dom 它有 "history" props 可以用来过渡到新路由
history.push('/newRoute')
我通过从 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'))
这似乎对我有用
function (error) {
var accessDenied = error.toString().indexOf("401");
if (accessDenied !== -1) {
console.log('ACCESS DENIED')
return window.location.href = '/accessdenied'
}
});
接受的答案没有解决我的问题。在 axios 上花费了一些时间,并在拦截器未触发的情况下提交了票据,我发现,axios 不支持像上面描述的那样全局装饰拦截器。对于未来的读者,请记住,axios 已将此 global interceptor
标记为功能。所以也许我们将来会得到它。供参考:https://github.com/axios/axios/issues/993。
我确实有一个用于所有 api 调用的 axios 实例,所以我解决了在其中定义拦截器的问题。
这非常有效。
window.location.href = `${process.env.REACT_APP_BASE_HREF}/login`;
刚刚意识到问题是针对 react router 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
}
plugins/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
这是对我有用的已接受答案的修改版本。
使用 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(......);
我想在收到 403 错误时在 axios 拦截器中进行重定向。但是我如何访问 React 组件之外的历史记录?
在
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);
});
我找到的最佳解决方案是在我的主要 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');
}
我通过从组件树外部访问我的 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 时,它会将我的 isLogged
属性 在我的 session
reducer 设置为 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);
}
希望对您有所帮助!
我正在使用 react-router-dom 它有 "history" props 可以用来过渡到新路由
history.push('/newRoute')
我通过从 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'))
这似乎对我有用
function (error) {
var accessDenied = error.toString().indexOf("401");
if (accessDenied !== -1) {
console.log('ACCESS DENIED')
return window.location.href = '/accessdenied'
}
});
接受的答案没有解决我的问题。在 axios 上花费了一些时间,并在拦截器未触发的情况下提交了票据,我发现,axios 不支持像上面描述的那样全局装饰拦截器。对于未来的读者,请记住,axios 已将此 global interceptor
标记为功能。所以也许我们将来会得到它。供参考:https://github.com/axios/axios/issues/993。
我确实有一个用于所有 api 调用的 axios 实例,所以我解决了在其中定义拦截器的问题。
这非常有效。
window.location.href = `${process.env.REACT_APP_BASE_HREF}/login`;
刚刚意识到问题是针对 react router 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
}
plugins/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
这是对我有用的已接受答案的修改版本。
使用 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(......);