如何在 React 组件之外访问历史对象

How to Access History Object Outside of a React Component

首先,我对 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 组件外部的历史对象?

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

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

在您的项目中创建一个名为 history.js 的文件,内容如下:

import { createBrowserHistory } from 'history';

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

找到工作示例

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

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

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
  );
}

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

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

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'

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

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

/*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 并使用它。

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

router/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");

一个简单的方法是 useHistory() in App.js 然后使用 render 并将 history 作为组件的属性传递:


function App() {
  const history = useHistory();
<Router>
 <Route
 path={nav.multiCategoriesNoTimer}
 render={() => <MultiCategoriesNoTimer history={history} />}
/>
</Router>
}
const MixMultiGameNoTimer = (props: any) => {
if (true) {
    return (
      <NoQuestionsHereScreen history={props.history} />
    );
  }
}
const NoQuestionsHereScreen = (props: any) => {
  return (
    <div className='no-questions-here' >
      <Button
        title="Go back"
        onClick={() => props.history.push(nav.home)}
      />
    </div>
  );
};

有一些钻孔,但它有效,而且对于许多未来版本也是如此>

我创建了一个解决方案来解决这个问题。 Access react router dom history object outside React component

我认为这种方法适用于 React-router v4 和 v5。