对axios封装中,在登录token过期,返回401状态码,react-router怎么进行路由跳转

#1

对axios封装中,在登录token过期,返回401状态码,react-router怎么进行路由跳转。

axios.interceptors.response.use(
    response => {
        return response;
    },
    error => {
        fakeAuth.signout();
        redirect();
        if (error.response) {
            switch (error.response.status) {
                case 401:
                    fakeAuth.signout();
                    redirect();
                    break;
                default:
            }
            const message = error.response.data.message ?
                error.response.data.message :
                error.response.status === 401 ?
                "登录过期,请重新登录" :
                "服务器异常";
            Message.error(message);
        }
        return Promise.reject(error);
    }
);
#2

window.location.href

#3

除了window.location.href进行强行跳转,没有其他的方法吗?

#4

有用 redux之类的第三方存储吗, 有的话就, 不需要借助router, 只要收到401请求的时候, 修改redux auth.isTokenTimeout 为 true, 界面就跳到超时登陆了, 而且url没改动,这样登陆成功后界面还能依据url自动还原, 程序入口代码如下

export const AuthorizeApplication = connect(x=> x.auth)(function Authorize({isLogin, isTokenTimeout}){
    if (isTokenTimeout) { return <TokenTimeoutSign /> }
    if (!isLogin) { return <Sign /> }
    return <Application />
})
1 Like
#5

谢谢,这个方法的确是不错的:grinning::grinning::grinning::grinning:

#6

isTokenTimeout默认是false,表示未过期,通过store.dispatch改变isTokenTimeout后出现

Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

想问一下按照你的说法,这种写法正确吗??

//包装的私密路由
import React from 'react';
import {Route,Redirect} from 'react-router-dom';
import {fakeAuth} from '../util/fakeAuth';
import {connect} from 'react-redux';
const PrivateRoute = ({component:Component,...rest})=>{
    return (
        <Route
            {...rest}
            render = {props => (fakeAuth.authenticate() && !rest.isTokenTimeout)?(<Component {...props}/>):(
                <Redirect to={{
                    pathname:"/login",
                    state:{from:props.location}
                }}/>
            )}
        ></Route>
    )
    
}
function mapStateToProps(state){
    console.log(state);
    return {
        isTokenTimeout:state.isTokenTimeout
    }
}
export default connect(mapStateToProps)(PrivateRoute);



//封装的axios
import axios from "axios"; 
import {fakeAuth} from '../util/fakeAuth';
import {message as Message} from 'antd';
import {timeout,baseURL} from "./config.js";
import {store} from '../redux/index';
axios.defaults.timeout = timeout;
axios.defaults.baseURL = baseURL;
axios.interceptors.request.use(
    config => {
        if (fakeAuth.authenticate()) {
            config.headers.Authorization = `Bearer ${sessionStorage.getItem('loginToken')}`;
        }
        return config;
    },
    error => {
        return Promise.reject(error);
    }
);
axios.interceptors.response.use(
    response => {
        return response;
    },
    error => {
        store.dispatch({type:'CHANGETIMEOUT'})
        if (error.response) {
            switch (error.response.status) {
                case 401:
                    // fakeAuth.signout();
                    // history.push('/login');
                    break;
                default:
            }
            const message = error.response.data.message ?error.response.data.message :"服务器异常";
            Message.error(message);
        }
        return Promise.reject(error);
    }
);
const gl_ajax = params => {
    return axios({
            method: params.method.toLowerCase(),
            url: `${axios.defaults.baseURL}${params.url}`,
            data: params.method !== "get" ? params.data : "",
            params: params.method === "get" ? params.data : "",
            responseType: params.file ? "blob" : ""
        })
        .then(res => {
            params.success && params.success(res);
        })
        .catch(err => {
            params.error && params.error(err);
        });
};
export default gl_ajax;
#7

你这个问题是循环调用, 生命周期内反复修改state,或者redux。
没有具体代码不知道问题在哪里。

#8

[数据驱动视图] 你需要好好理解这句话,可能是初学者吧,首先登陆和不登录就是一个状态,你用这来控制是否会弹出到登陆页,当你axios捕获到token失效的时候,你当然就可以修改登陆状态来控制页面跳转。

#9

你这个写法的逻辑,怎么说呢, 其实可以不那么复杂。

const App = connect(x=> x.isTimeout)(function Application({ isTimeout }){
   if (location.pathname !== "/login" && fakeAuth.authenticate() && isTimeout ){
     return <Redirect to="/login" /> 
   }
   return (
      <Switch>
         <Route path="/login" component={Login} />
         <Route path="/product" component={ProductList} />
       </Switch>
   );
})

但是其实我还是建议你你,第一 fakeauth不要这么写, 要么放在Context中,要么放在redux里面, 现在react的context hooks 使用起来还是很方便的。react useContext api

Example

// Auth.jsx
export const AuthContext = React.createContext({children});
export const useAuth = ()=> React.useContext(AuthContext);
export const AuthProvider = ({localStorageKey = "token", children })=> {
   const [token, setToken] = React.useState(()=> localStorage.getItem(localStorageKey)||"");
   const saveToken = React.useCallback((v)=> {
       localStorage.setItem(localStorageKey, JSON.stringify(v));
      setToken(v);
   }, [localStorageKey])
   const [isLogin, setIsLogin] = React.useState(false);
   const login = React.useCallback((nextToken)=> { 
      setIsLogin(true);
      saveToken(nextToken)
   }, []);
   const logout = React.useCallback(()=> {
      setIsLogin(false);
      saveToken("")
   }, [])
   const value = { token, saveToken, login, logout, isLogin }
   return <AuthContext.Provider value={value} children={children} />
}
// ajax.jsx
import { useAuth } from "./Auth"
import axios from "axios"
const AjaxContext = React.createContext();
export const useAjax = ()=> React.useContext(AjaxContext);
export function AjaxProvider({timeout, baseURL, children}){
    const instance = React.useMemo(()=> axios.create({timeout, baseURL}), [timeout, baseURL]);
    const {isLogin, saveToken, token} = useAuth();
    React.useEffect(()=> {
      // request 拦截器
      const onError = (err)=> Promise.reject(err)
      const onRequest = (config)=> {
            if (token) {
               config.headers.Authorization = `Bearer ${token}`;
           }
           return config;
      }
      const flag = axios.interceptors.request.use(onRequest, onError);
      return ()=> instance.interceptors.request.eject(flag)
    }, [instance, isLogin, token])
   React.useEffect(()=> {
      const onResp = (res)=> res
      const onError = (error)=> {
           if (err.response) {
               if (error.response.status === 401){ saveToken("") }
               Message.error(error.response.data.message || "服务器异常");
           }
      }
      const flag = axios.interceptors.response.use(onResp, onError);
      return ()=> instance.interceptors.response.eject(flag)
   }, [instance, saveToken])
   const ajax = React.useCallback((params)=> {
      const onSuccess = res=> params.success && params.success(res);
      const onError = err=> params.error && params.error(err);
      return instance({
            method: params.method.toLowerCase(),
            url: `${axios.defaults.baseURL}${params.url}`,
            data: params.method !== "get" ? params.data : "",
            params: params.method === "get" ? params.data : "",
            responseType: params.file ? "blob" : ""
      }).then(onSuccess, onError)
   }, [instance]);
   const value = { ajax, axios: instance }
   return <AjaxContext.Provider value={value} children={children} />
}
// ProductPage.jsx
import { useAjax } from "./Ajax"
export function ProductPage({match: { params: { id } }}){
    const { ajax } = useAjax();
    const [data, setData] = React.useState(null);
    const [error, setError] = React.useState(null);
    React.useEffect(()=> {
       ajax({url: `/product/${id}`, error: setError, success: (res)=> setData(res.data)})
    }, [id])
    return (
       <>
          {!error && !data && <Loading /> }
          {error && <ErrorDisplayer error={error}/>}
          {data && <DataDsplayer data={data}}/>}
       </>
    )
}
// index.jsx
ReactDOM.render(
  <AuthProvider localStorageKey="fake_token">
     <AjaxProvider baseURL="/api" timeout={30000}>
       <BrowserRouter>
          <Application />
       </BrowserRouter>
     </AjaxProvider>
  </AuthProvider>
, ROOT_DOM)
// Application.jsx
import {useAuth} from "./useAuth";
export function Application(){
     return (
        <Switch>
           <Route path="/home" component={HomePage} />
           <Route path="/login" component={LoginPage} />
           <AuthRoute path="/product/:id" component={ProductPage} />
        </Switch>
     )
}
function LoginRedirect(props) {
   // 你确定 state 有用吗, 我没看到有这个api....., 
   // 觉得还是把url改成 `/login?redirect=${encodeURLComponent(location.href)}`
   // 这种方式会好点, 在login成功后分析url来处理
   return <Redirect path="/login" state={{from: props.location}} />
}
function AuthRoute = (props)=> {
   const { token, isLogin } = useAuth();
   const component = !token || !isLogin ? LoginRedirect : props.component;
   return <Route {...props} component={component} />
}

以上都是裸写的, 大概跑步通,意思就是这个意思,还有,typescript大法好,你有空可以学学。
还有就是我比较喜欢用 useApi,
const { getProduct, removeProduct } = useApi();
这种,直接封装干净,然后,在开发阶段可以做mock

const sleep = (ms)=> new Promise(r=> setTimeout(r, ms))
export function MockApiProvider({delay = 400}){
    const getProduct = async (id)=> {
        await sleep(delay);
        return { id, name: "mock product name" }
    }
    const removeProduct = ()=> sleep(delay).then(()=> throw new Error("权限不足不能删除"))
    return <ApiContext.Provider value={{getProduct, removeProduct}} children={children}/>
}
#10

好的,谢谢·

#11

你好我想问下 我下一次跳转 this.props.push(‘/XXXX’); 报错 Cannot read property ‘push’ of undefined