如何访问React组件之外的历史记录对象

时间:2017-05-24 08:52:58

标签: react-router react-router-v4

首先,我对withRouter HoC很熟悉,但是,在这种情况下,它没有帮助,因为我不想访问组件中的history对象

我正在尝试实现一种机制,如果我从API端点收回401,则会将用户重定向到登录页面。为了发出http请求,我正在使用axios。我需要覆盖大约60个端点,这些端点在我的应用程序中用于十几个组件。

我想为axios实例对象创建一个装饰器函数:

1. makes the request
2. if fail && error_code = 401, update user route to `/login`
3. if success, return promise

我遇到的问题是更新用户的路线。以前,在react-router-v3中,我可以直接从react-router包中导入browserHistory对象,这已不再可能。

所以,我的问题是,如何在不通过调用堆栈的情况下访问React组件之外的历史对象?

5 个答案:

答案 0 :(得分:16)

react-router v4还提供了一种通过history包共享历史记录的方法,即createBrowserHistory()函数。

重要的是确保在您的应用中共享相同的历史记录对象。为此,您可以利用节点模块是单例的事实。

在项目中创建一个名为history.js的文件,其中包含以下内容:

import createBrowserHistory from 'history/createBrowserHistory';

const history = createBrowserHistory();
export default history;

然后您可以通过以下方式将其导入您的应用程序:

import history from "./history.js";

请注意,只有Router接受history道具(BrowserRouter没有),所以请务必相应地更新路由器JSX:

import { Router } from "react-router-dom";
import history from "./history.js";

// and then in your JSX:
return (
  <Router history={history}>
    {/* routes as usuall */}
  </Router>
)

可以在https://codesandbox.io/s/owQ8Wrk3

找到一个工作示例

答案 1 :(得分:1)

今天,我遇到了同样的问题。也许我的解决方案可以帮助其他人。

src / axiosAuthenticated.js

import axios from 'axios';
import { createBrowserHistory } from 'history';


const UNAUTHORIZED = 401;

axios.interceptors.response.use(
  response => response,
  error => {
    const {status} = error.response;
    if (status === UNAUTHORIZED) {
      createBrowserHistory().push('/');
      window.location.reload();
    }
    return Promise.reject(error);
 }
);

export default axios;

此外,如果您想拦截任何添加存储在LocalStorage中的令牌的请求:

let user = JSON.parse(localStorage.getItem('user'));

var authToken = "";
if (user && user.token)
  authToken = 'Bearer ' + user.token;

axios.defaults.headers.common = {'Authorization': `${authToken}`}

要使用它,而不是从“ axios”导入,而是从“ axiosAuthenticated”导入,如下所示:

import axios from 'utils/axiosAuthenticated'

答案 2 :(得分:0)

我刚刚遇到了同样的问题,下面是我用来解决此问题的解决方案。

我最终创建了一个工厂函数,该函数返回一个具有我所有服务功能的对象。为了调用此工厂函数,必须提供具有以下形状的对象。

interface History {
    push: (location: string) => void;
}

这是我的工厂功能的简化版本。

const services = {};

function servicesFactory(history: History) {
    const countries = countriesFactory(history);
    const local = {
        ...countries,
    };
    Object.keys(local).forEach(key => {
        services[key] = local[key];
    });

}

现在定义了此功能的文件将导出2件事。

1)此工厂功能

2)服务对象。

这是国家/地区服务的外观。


function countriesFactory(h: History): CountriesService {
    const countries: CountriesService = {
        getCountries() {
            return request<Countries>({
                method: "get",
                endpoint: "/api/countries",
            }, h)
        }
    }
    return countries;
}

最后,这就是我的request函数的样子。

function request<T>({ method, endpoint, body }: Request, history: History): Promise<Response<T>> {
    const headers = {
        "token": localStorage.getItem("someToken"),
    };
    const result: Response<T> = {
        data: null,
        error: null,
    };
    return axios({
        url: endpoint,
        method,
        data: body,
        headers,
    }).then(res => {
        result.data = res.data;
        return result;
    }).catch(e => {
        if (e.response.status === 401) {
            localStorage.clear();
            history.push("/login");
            return result;
        } else {
            result.error = e.response.data;
            return result;
        }
    });
}

您可以看到request函数exepcts传递了历史对象,该对象将从服务中获取,服务将从服务工厂获取它。

现在最酷的部分是,我只需要调用此工厂函数并将历史对象在整个应用程序中传递一次即可。之后,我可以简单地导入services对象并对其使用任何方法,而不必担心将历史对象传递给它。

这是我调用服务工厂函数的代码。

const App = (props: RouteComponentProps) => {
  servicesFactory(props.history);
  return (
    // my app and routes
  );
}

希望发现此问题的其他人会发现这很有用。

答案 3 :(得分:0)

我在这里提供我的解决方案,因为接受的答案没有解决新版本的 React Router,他们需要重新加载页面才能使该解决方案起作用。

我使用了相同的 BrowserRouter。我创建了一个带有静态函数和成员历史实例的类。

/*history.js/

class History{
   static historyInstance = null;

   static push(page) {
        History.historyInstance.push(page);
   }

}

/*app-router.js/

const SetHistoryInstance = () => {
   History.historyInstance = useHistory();
   return (null);
};

const AppRouter = () => {
  
  return (
  <BrowserRouter>
    <SetHistoryInstance></SetHistoryInstance>
    <div>
      <Switch>
        <Route path={'/'} component={Home} />
        <Route path={'/data'} component={Data} exact />
      </Switch>
    </div>
  </BrowserRouter>
)};

现在您可以在应用的任何位置导入 history.js 并使用它。

答案 4 :(得分:0)

这是一个在最新版本(5.2.0)中对我有用的解决方案

路由器/index.js

import { BrowserRouter, Switch } from "react-router-dom";
import { Routes } from "./routes";

export const Router = () => {
  return (
    <BrowserRouter>
      <Switch>
        <Routes />
      </Switch>
    </BrowserRouter>
  );
};

router/routes.js

import React, { createRef } from "react";
import { Route, useHistory } from "react-router-dom";
import { PageOne, PageTwo, PageThree } from "../pages";

export const historyRef = createRef();

export const Routes = () => {
  const history = useHistory();
  historyRef.current = history;

  return (
    <>
      <Route exact path="/" component={PageOne} />
      <Route exact path="/route-one" component={PageTwo} />
      <Route exact path="/route-two" component={PageThree} />
    </>
  );
};

并如下使用

historyRef.current.replace("/route-two");