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



首先,我对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

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

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'

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

路由器/索引.js

import { BrowserRouter, Switch } from "react-router-dom";
import { Routes } from "./routes";
export const Router = () => {
return (
<BrowserRouter>
<Switch>
<Routes />
</Switch>
</BrowserRouter>
);
};

路由器/路由.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");

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

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

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函数要求将历史对象传递给它,它将从服务获取,服务将从服务工厂获取它。

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

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

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

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

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

我使用了相同的浏览器路由器。我创建了一个具有静态函数和成员历史记录实例的类。

/*历史.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>
)};

现在,您可以在应用中的任何位置导入历史记录.js并使用它。

一种简单的方法是useHistory()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>
);
};

有一些钻孔,但它有效,也适用于许多未来的版本>

我创建了一个可以解决此问题的解决方案。 在 React 组件外部访问 react 路由器 dom 历史对象

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

最新更新