我刚刚将 react-router
从 v3 替换为 v4。
但我不确定如何以编程方式在 Component
的成员函数中导航。即在 handleClick()
函数中,我想在处理一些数据后导航到 /path/some/where
。我曾经这样做过:
import { browserHistory } from 'react-router'
browserHistory.push('/path/some/where')
但是我在 v4 中找不到这样的接口。如何使用 v4 进行导航?
Component
不同的地方访问它怎么办?例如,在 redux 操作中。
如果您以浏览器环境为目标,则需要使用 react-router-dom
包,而不是 react-router
。他们遵循与 React 相同的方法,以便将核心 (react
) 和平台特定代码 (react-dom
, react-native
) 分开,但您不需要安装两个单独的包,因此环境包包含您需要的一切。您可以将其添加到您的项目中:
yarn add react-router-dom
或者
npm i react-router-dom
您需要做的第一件事是提供一个 <BrowserRouter>
作为应用程序中最顶层的父组件。 <BrowserRouter>
使用 HTML5 history
API 并为您管理它,因此您不必担心自己实例化它并将其作为道具传递给 <BrowserRouter>
组件(正如您在以前的版本中需要做的那样)。
在 V4 中,为了以编程方式导航,您需要访问 history
对象,该对象可通过 React context
获得,只要您有一个 <BrowserRouter>
provider 组件作为您的最顶层父组件应用。该库通过上下文公开 router
对象,该对象本身包含 history
作为属性。 history
界面提供了多种导航方法,例如 push
、replace
和 goBack
等。您可以查看属性和方法的完整列表 here。
Redux/Mobx 用户的重要提示
如果您在应用程序中使用 redux 或 mobx 作为您的状态管理库,您可能会遇到组件应该具有位置感知能力但在触发 URL 更新后不会重新渲染的问题
发生这种情况是因为 react-router
使用上下文模型将 location
传递给组件。
connect 和 observer 都创建组件,其 shouldComponentUpdate 方法对其当前 props 和下一个 props 进行浅比较。这些组件只会在至少一个道具发生变化时重新渲染。这意味着为了确保它们在位置更改时更新,需要为它们提供一个在位置更改时更改的道具。
解决此问题的两种方法是:
将连接的组件包装在无路径的
用 withRouter 高阶组件包装连接的组件,实际上具有相同的效果并将位置作为道具注入
除此之外,有四种以编程方式导航的方法,按推荐排序:
1.- 使用
<路线/>
<路线>
任何地方
路线
匹配
地点
历史
推
代替
回去
历史
通过使用 component
、render
或 children
道具,有 3 种使用 Route
呈现内容的方法,但不要在同一个 Route
中使用多个。选择取决于用例,但基本上前两个选项只会在 path
与 url 位置匹配时呈现您的组件,而使用 children
时,无论路径与位置是否匹配都会呈现组件(对于根据 URL 匹配调整 UI)。
如果您想自定义组件渲染输出,您需要将组件包装在一个函数中并使用 render
选项,以便将您想要的任何其他道具传递给您的组件,除了 { 2}、location
和 history
。一个例子来说明:
import { BrowserRouter as Router } from 'react-router-dom'
const ButtonToNavigate = ({ title, history }) => (
<button
type="button"
onClick={() => history.push('/my-new-location')}
>
{title}
</button>
);
const SomeComponent = () => (
<Route path="/" render={(props) => <ButtonToNavigate {...props} title="Navigate elsewhere" />} />
)
const App = () => (
<Router>
<SomeComponent /> // Notice how in v4 we can have any other component interleaved
<AnotherComponent />
</Router>
);
2.- 使用 withRouter HoC
这个高阶组件将注入与 Route
相同的道具。但是,它具有每个文件只能有 1 个 HoC 的限制。
import { withRouter } from 'react-router-dom'
const ButtonToNavigate = ({ history }) => (
<button
type="button"
onClick={() => history.push('/my-new-location')}
>
Navigate
</button>
);
ButtonToNavigate.propTypes = {
history: React.PropTypes.shape({
push: React.PropTypes.func.isRequired,
}),
};
export default withRouter(ButtonToNavigate);
3.- 使用重定向组件
<重定向>
默认
至
地点
将新条目推入历史
推
真的
<Redirect to="/your-new-location" push />
4.- 通过上下文手动访问路由器
语境
const ButtonToNavigate = (props, context) => (
<button
type="button"
onClick={() => context.router.history.push('/my-new-location')}
>
Navigate to a new location
</button>
);
ButtonToNavigate.contextTypes = {
router: React.PropTypes.shape({
history: React.PropTypes.object.isRequired,
}),
};
毋庸置疑,还有其他用于非浏览器生态系统的路由器组件,例如 <NativeRouter>
,它复制了导航堆栈在内存中并以 React Native 平台为目标,可通过 react-router-native
包获得.
如需进一步参考,请随时查看 official docs。还有一个由该库的一位合著者制作的 video,它提供了对 react-router v4 的非常酷的介绍,突出了一些主要变化。
完成它的最简单方法:
this.props.history.push("/new/url")
笔记:
您可能希望将历史道具从父组件传递到要调用该操作的组件(如果它不可用)。
this.props.history
,那么您可以先import {withRouter} from 'react-router-dom'
,然后是 export default withRouter(MyComponent)
(或 const MyComponent = withRouter(...)
),它会在组件的 props 中插入 history
项。
history.push
上重新挂载并触发更新,就像我们点击 <Link>
时一样
我在迁移到 React-Router v4 时遇到了类似的问题,所以我将尝试在下面解释我的解决方案。
请不要将此答案视为解决问题的正确方法,我想随着 React Router v4 变得更加成熟并离开测试版,很有可能会出现更好的情况(它甚至可能已经存在,我只是没有发现) .
对于上下文,我遇到了这个问题,因为我偶尔使用 Redux-Saga
以编程方式更改历史对象(比如当用户成功验证时)。
在 React Router 文档中,查看 <Router>
component,您可以看到您可以通过道具传递您自己的历史记录对象。这是解决方案的精髓 - 我们从 global 模块向 React-Router
提供历史对象。
脚步:
安装历史 npm 模块 - yarn add history 或 npm install history --save 在您的 App.js 级别文件夹中创建一个名为 history.js 的文件(这是我的偏好) // src/history.js import createHistory from 'history/createBrowserHistory '; export default createHistory();` 像这样将此历史对象添加到您的路由器组件 // src/App.js import history from '../your/path/to/history.js;'
现在一切都应该保持同步,并且您还可以访问以编程方式设置历史对象的方法,而不是通过组件/容器。
Route
组件传递的道具。
TL;博士:
if (navigate) {
return <Redirect to="/" push={true} />
}
简单而明确的答案是您需要将 <Redirect to={URL} push={boolean} />
与 setState()
结合使用
push: boolean - 如果为 true,重定向会将新条目推送到历史记录中,而不是替换当前条目。
import { Redirect } from 'react-router'
class FooBar extends React.Component {
state = {
navigate: false
}
render() {
const { navigate } = this.state
// here is the important part
if (navigate) {
return <Redirect to="/" push={true} />
}
// ^^^^^^^^^^^^^^^^^^^^^^^
return (
<div>
<button onClick={() => this.setState({ navigate: true })}>
Home
</button>
</div>
)
}
}
PS。该示例使用 ES7+ Property Initializers 来初始化状态。如果您有兴趣,也可以看看 here。
history.push()
方法之一。
如果您正在使用函数组件,请使用 useHistory 钩子
您可以使用 useHistory
挂钩来获取 history
实例。
import { useHistory } from "react-router-dom";
const MyComponent = () => {
const history = useHistory();
return (
<button onClick={() => history.push("/about")}>
Click me
</button>
);
}
useHistory
挂钩可让您访问可用于导航的历史实例。
在页面组件中使用历史属性
React Router 会向页面组件注入一些属性,包括 history
。
class HomePage extends React.Component {
render() {
const { history } = this.props;
return (
<div>
<button onClick={() => history.push("/projects")}>
Projects
</button>
</div>
);
}
}
用Router包裹子组件以注入路由器属性
withRouter
包装器将路由器属性注入组件。例如,您可以使用此包装器将路由器注入到放置在用户菜单中的注销按钮组件。
import { withRouter } from "react-router";
const LogoutButton = withRouter(({ history }) => {
return (
<button onClick={() => history.push("/login")}>
Logout
</button>
);
});
export default LogoutButton;
您也可以简单地使用道具来访问历史对象:this.props.history.push('new_url')
this.props.history
,那么您可以先import {withRouter} from 'react-router-dom'
,然后是 export default withRouter(MyComponent)
(或 const MyComponent = withRouter(...)
),它会在组件的 props 中插入 history
项。
第 1 步:上面只有一件事要导入:
import {Route} from 'react-router-dom';
第 2 步:在您的路线中,传递历史记录:
<Route
exact
path='/posts/add'
render={({history}) => (
<PostAdd history={history} />
)}
/>
第 3 步:history 被接受为下一个组件中 props 的一部分,因此您可以简单地:
this.props.history.push('/');
这很容易而且非常强大。
我的回答类似于Alex's。我不确定为什么 React-Router 让这变得如此不必要的复杂。为什么我必须用 HoC 包装我的组件才能访问本质上是全局的?
无论如何,如果您看看他们是如何实现 <BrowserRouter>
的,它只是围绕 history 的一个小包装。
我们可以提取历史记录,以便我们可以从任何地方导入它。然而,诀窍是,如果您正在执行服务器端呈现并尝试import
历史记录模块,它将无法工作,因为它使用仅浏览器的 API。但这没关系,因为我们通常只重定向以响应点击或其他一些客户端事件。因此,伪造它可能是可以的:
// history.js
if(__SERVER__) {
module.exports = {};
} else {
module.exports = require('history').createBrowserHistory();
}
在 webpack 的帮助下,我们可以定义一些 vars 以便我们知道我们所处的环境:
plugins: [
new DefinePlugin({
'__SERVER__': 'false',
'__BROWSER__': 'true', // you really only need one of these, but I like to have both
}),
现在你可以
import history from './history';
从任何地方。它只会在服务器上返回一个空模块。
如果您不想使用这些魔法变量,您只需在需要它的全局对象中require
(在您的事件处理程序中)。 import
不起作用,因为它只在顶层起作用。
我认为@rgommezz 涵盖了大多数情况,减去我认为非常重要的情况。
// history is already a dependency or React Router, but if don't have it then try npm install save-dev history
import createHistory from "history/createBrowserHistory"
// in your function then call add the below
const history = createHistory();
// Use push, replace, and go to navigate around.
history.push("/home");
这使我可以编写一个带有操作/调用的简单服务,我可以调用它来从我想要的任何组件进行导航,而无需在我的组件上做很多 HoC...
目前尚不清楚为什么以前没有人提供此解决方案。希望对您有所帮助,如果您发现任何问题,请告诉我。
@withRouter
来装饰任何依赖于路由的组件)。有任何想法吗?
您可以通过这种方式有条件地导航
import { useHistory } from "react-router-dom";
function HomeButton() {
const history = useHistory();
function handleClick() {
history.push("/path/some/where");
}
return (
<button type="button" onClick={handleClick}>
Go home
</button>
);
}
这有效:
import { withRouter } from 'react-router-dom';
const SomeComponent = withRouter(({ history }) => (
<div onClick={() => history.push('/path/some/where')}>
some clickable element
</div>);
);
export default SomeComponent;
我已经测试 v4 几天了,而且.. 到目前为止我很喜欢它!一段时间后才有意义。
我也有同样的问题,我发现像下面这样处理它效果最好(甚至可能是它的意图)。它使用状态、三元运算符和 <Redirect>
。
在构造函数()
this.state = {
redirectTo: null
}
this.clickhandler = this.clickhandler.bind(this);
在渲染()
render(){
return (
<div>
{ this.state.redirectTo ?
<Redirect to={{ pathname: this.state.redirectTo }} /> :
(
<div>
..
<button onClick={ this.clickhandler } />
..
</div>
)
}
在 clickhandler()
this.setState({ redirectTo: '/path/some/where' });
希望能帮助到你。让我知道。
我为此苦苦挣扎了一段时间——事情如此简单,却又如此复杂,因为 ReactJS 只是一种完全不同的 Web 应用程序编写方式,它对我们这些老年人来说非常陌生!
我创建了一个单独的组件来抽象出混乱:
// LinkButton.js
import React from "react";
import PropTypes from "prop-types";
import {Route} from 'react-router-dom';
export default class LinkButton extends React.Component {
render() {
return (
<Route render={({history}) => (
<button {...this.props}
onClick={() => {
history.push(this.props.to)
}}>
{this.props.children}
</button>
)}/>
);
}
}
LinkButton.propTypes = {
to: PropTypes.string.isRequired
};
然后将其添加到您的 render()
方法中:
<LinkButton className="btn btn-primary" to="/location">
Button Text
</LinkButton>
由于没有其他方法可以处理这种可怕的设计,我编写了一个使用 withRouter
HOC 方法的通用组件。下面的示例包装了一个 button
元素,但您可以更改为您需要的任何可点击元素:
import React from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
const NavButton = (props) => (
<Button onClick={() => props.history.push(props.to)}>
{props.children}
</Button>
);
NavButton.propTypes = {
history: PropTypes.shape({
push: PropTypes.func.isRequired
}),
to: PropTypes.string.isRequired
};
export default withRouter(NavButton);
用法:
<NavButton to="/somewhere">Click me</NavButton>
this.props.history.push("/url")
如果您还没有在组件中找到 this.props.history 可用,那么试试这个
import {withRouter} from 'react-router-dom'
export default withRouter(MyComponent)
有时我更喜欢通过应用程序然后通过按钮切换路由,这是一个对我有用的最小工作示例:
import { Component } from 'react'
import { BrowserRouter as Router, Link } from 'react-router-dom'
class App extends Component {
constructor(props) {
super(props)
/** @type BrowserRouter */
this.router = undefined
}
async handleSignFormSubmit() {
await magic()
this.router.history.push('/')
}
render() {
return (
<Router ref={ el => this.router = el }>
<Link to="/signin">Sign in</Link>
<Route path="/signin" exact={true} render={() => (
<SignPage onFormSubmit={ this.handleSignFormSubmit } />
)} />
</Router>
)
}
}
对于那些需要在使用 React Router
或 React Router Dom
完全初始化路由器之前重定向的人,您可以通过简单地访问历史对象并将新状态推送到 app.js
的构造中来提供重定向。考虑以下:
function getSubdomain(hostname) {
let regexParse = new RegExp('[a-z\-0-9]{2,63}\.[a-z\.]{2,5}$');
let urlParts = regexParse.exec(hostname);
return hostname.replace(urlParts[0], '').slice(0, -1);
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
hostState: true
};
if (getSubdomain(window.location.hostname).length > 0) {
this.state.hostState = false;
window.history.pushState('', '', './login');
} else {
console.log(getSubdomain(window.location.hostname));
}
}
render() {
return (
<BrowserRouter>
{this.state.hostState ? (
<div>
<Route path="/login" component={LoginContainer}/>
<Route path="/" component={PublicContainer}/>
</div>
) : (
<div>
<Route path="/login" component={LoginContainer}/>
</div>
)
}
</BrowserRouter>)
}
}
在这里,我们想要更改依赖于子域的输出路由,通过在组件渲染之前与历史对象交互,我们可以有效地重定向,同时仍然保持路由完好无损。
window.history.pushState('', '', './login');
react-router-dom
导入withRouter
react-router-dom
导入withRouter
。 history.push 确实更改了 url,但它似乎没有加载<Route>
,直到我强制刷新页面......@observer
装饰的 React 组件中有<Router>
,它触发了 this issue。解决方法是在每个这样的 React 组件上都有@withRouter
。withRouter
只是一个在底层使用Route
的 HOC。这意味着它只使用 3 props, history, match, and location。在上面的示例中,push
似乎是withRouter
将添加到ButtonToNavigate
的道具,但事实并非如此。props.history.push
必须改为使用。希望这可以帮助其他有点困惑的人。browserHistory.push('/path/some/where')
似乎要简单得多。作者试图阻止命令式编程,但有时它会更好!