以编程方式导航使用反应路由器V4

我刚刚从v3replacereact-router到v4。
但我不知道如何以编程方式在Component的成员函数中导航。 即在handleClick()函数我想导航到/path/some/where处理一些数据。 我曾经这样做:

 import { browserHistory } from 'react-router' browserHistory.push('/path/some/where') 

但是我在v4中找不到这样的界面。
我如何使用v4导航?

如果您的目标是浏览器环境,则需要使用react-router-dom软件包,而不是react-router 。 他们采取与React相同的方法,以分离核心,( react )和特定于平台的代码( react-domreact-native ),其细微区别在于你不需要安装两个独立的包,所以环境包包含你需要的一切。 您可以将其添加到您的项目中:

yarn add react-router-dom

要么

npm i -S react-router-dom

您需要做的第一件事是提供一个<BrowserRouter>作为您的应用程序中最顶级的父组件。 <BrowserRouter>使用HTML5 history API并为您进行pipe理,因此您不必担心自己将其实例化并将其作为道具传递给<BrowserRouter>组件(正如您在以前版本中所做的那样)。

在V4中,为了以编程方式进行导航,只要您有一个<BrowserRouter> 提供程序组件作为您的应用程序中最顶级的父项,就需要访问通过React context可用的history对象。 该库通过上下文暴露router对象,该对象本身包含作为属性的historyhistory界面提供了几种导航方法,例如pushreplacegoBack等等。 您可以在这里查看整个属性和方法列表。

Redux / Mobx用户的重要说明

如果在应用程序中使用redux或mobx作为状态pipe理库,则可能遇到组件位置感知问题,这些问题在触发URL更新后不会重新呈现

这是因为react-router使用上下文模型将location传递给组件。

连接和观察者都创build组件,它们的shouldComponentUpdate方法对他们当前的道具和他们的下一个道具进行浅层的比较。 这些组件只有在至less有一个道具改变时才会重新渲染。 这意味着为了确保他们在位置变化时进行更新,当位置变化时,他们需要被赋予一个改变的道具。

解决这个问题的两种方法是:

  • 将你的连接组件包装在一个无path的<Route /> 。 当前location对象是<Route>传递给它所呈现的组件的道具之一
  • 使用withRouter高阶组件包装连接的组件,事实上它具有相同的效果,并将其作为道具注入location

除此之外,有四种方法可以通过编程方式进行导航,按照build议进行sorting:

1.-使用<Route>组件

它促进了声明式的风格。 在v4之前, <Route />组件被放置在组件层次结构的顶部,事先必须考虑到你的路由结构。 但是,现在您可以在树中的任何位置使用<Route>组件,从而可以根据URL有条件地进行更精细的控制。 Route注入matchlocationhistory作为道具到您的组件。 导航方法(例如pushreplacegoBack …)可作为history对象的属性。

有三种方法可以使用componentrenderchildren道具来渲染path,但是不要在同一个path中使用多个path。 select取决于用例,但前两个选项基本上只会呈现您的组件,如果path匹配url的位置,而对于children组件,将呈现无论path匹配的位置与否(有用的基础上调整UI在URL匹配上)。

如果你想自定义你的组件渲染输出 ,你需要将你的组件包装在一个函数中,并使用render选项,以传递到你的组件的任何其他道具,除了matchlocationhistory 。 一个例子来说明:

 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, }).isRequired, }; export default withRouter(ButtonToNavigate); 

3.-使用Redirect组件

渲染一个<Redirect>将导航到一个新的位置。 但请记住, 默认情况下 ,当前位置被replace为新的位置,如服务器端redirect(HTTP 3xx)。 新位置由prop提供,可以是string(要redirect到的URL)或location对象。 如果你想推入一个新的历史,而不是传递push道具,并将其设置为true

 <Redirect to="/your-new-location" push /> 

4.-通过上下文手动访问router

有些沮丧,因为上下文仍然是一个实验性的API,它可能会在未来的React版本中打破/改变

 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-router-native包提供React Native平台。

对于任何进一步的参考,不要犹豫,看看官方文件 。 还有一个由图书馆的合作者之一制作的video ,提供了一个非常酷的介绍,反应路由器v4,突出一些重大变化。

最简单的方法来完成它:

this.props.history.push("/new/url")

注意:

  • 如果不可用,您可能希望将history prop从父组件传递到要调用该行为的组件。

在迁移到React-Router v4时,我遇到了类似的问题,所以我会尝试在下面解释我的解决scheme。

请不要认为这个答案是解决问题的正确方法,我想有一个很好的机会,因为React Router v4变得更加成熟,并且会出现beta(它甚至可能已经存在,我只是没有发现它) 。

对于上下文,我有这个问题,因为我偶尔使用Redux-Saga以编程方式更改历史logging对象(说当用户成功validation)。

在React Router文档中,看看<Router> 组件 ,你可以看到你有能力通过prop来传递你自己的历史对象。 这是解决scheme的本质 – 我们将历史对象全局模块提供React-Router

脚步:

  1. 安装历史npm模块 – yarn add history npm install history --save
  2. App.js级别的文件夹中创build一个名为history.js文件(这是我的偏好)

     // src/history.js import createHistory from 'history/createBrowserHistory'; export default createHistory();` 
  3. 像这样将这个历史对象添加到你的路由器组件中

     // src/App.js import history from '../your/path/to/history.js;' <Router history={history}> // Route tags here </Router> 
  4. 通过导入您的全局历史logging对象,像以前一样调整url:

     import history from '../your/path/to/history.js;' history.push('new/path/here/'); 

一切都应该保持现在同步,并且你也有机会通过程序设置历史对象,而不是通过组件/容器。

TL; DR:

 if (navigate) { return <Redirect to="/" push={true} /> } 

简单的声明性答案是您需要使用<Redirect to={URL} push={boolean} />setState()

push:boolean –如果为true,redirect会将新条目推送到历史logging中,而不是replace当前的条目。


 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 +属性初始化程序来初始化状态。 如果你有兴趣,也可以在这里看看。

我的答案与Alex的相似。 我不确定为什么React-Router使这个如此复杂。 为什么我必须用HoC封装我的组件才能访问本质上是全局的?

无论如何,如果你看看他们如何实现<BrowserRouter> ,它只是一个关于历史的小包装。

我们可以把这个历史放一下,以便我们可以从任何地方导入它。 但是,诀窍是,如果您正在进行服务器端渲染,并且您尝试import历史logging模块,则它将无法工作,因为它使用纯浏览器API。 但这没关系,因为我们通常只会响应点击或其他客户端事件而redirect。 因此假冒它可能是可以的:

 // history.js if(__SERVER__) { module.exports = {}; } else { module.exports = require('history').createBrowserHistory(); } 

在webpack的帮助下,我们可以定义一些variables,所以我们知道我们在什么环境:

 plugins: [ new DefinePlugin({ '__SERVER__': 'false', '__BROWSER__': 'true', // you really only need one of these, but I like to have both }), 

现在你可以

 import history from './history'; 

从任何地方。 它只会返回服务器上的空模块。

如果你不想使用这些魔法variables,你只require在全局对象中需要它(在你的事件处理程序中)。 import将不起作用,因为它只能在顶层工作。

我已经testing了几天的v4,现在我爱它了! 这一段时间才有意义。

我也有同样的问题,我发现像下面的工作最好(甚至可能是如何打算)处理它。 它使用状态,一个三元运算符和<Redirect>

在构造函数()

 this.state = { redirectTo: null } this.clickhandler = this.clickhandler.bind(this); 

在render()

 render(){ return ( <div> { this.state.redirectTo ? <Redirect to={{ pathname: this.state.redirectTo }} /> : ( <div> .. <button onClick={ this.clickhandler } /> .. </div> ) } 

在点击处理程序()

  this.setState({ redirectTo: '/path/some/where' }); 

希望它有帮助。 让我知道。

也可以简单地使用道具:this.props.history.push('new_url')

有时候我更喜欢通过应用程序切换路线,然后通过button,这是一个最小的工作示例对我有用:

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

第一步:只有一件事情可以在上面导入:

  import {Route} from 'react-router-dom'; 

第2步:在你的路线,通过历史:

  <Route exact path='/posts/add' render={({history}) => ( <PostAdd history={history} /> .)}/> 

第3步:历史被接受为下一个组件的道具的一部分,所以你可以简单地:

  this.props.history.push('/'); 

这很容易,而且非常强大。

我在这方面挣扎了一段时间 – 这么简单,但又太复杂了,因为ReactJS只是一种完全不同的Web应用程序编写方式,对我们老年人来说是非常陌生的!

我创build了一个独立的组件来抽象掉这个混乱:

 // 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> 

由于没有其他方法来处理这个可怕的devise,我写了一个使用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>