使用React Router V4以编程方式导航


336

我刚刚react-router从v3 替换为v4。
但是我不确定如何以编程方式在的成员函数中进行导航Component。即在handleClick()功能上我想导航到/path/some/where处理一些数据后。我曾经通过以下方式做到这一点:

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

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


3
您可以通过props访问v4中的历史对象:this.props.history.push('/')
colemerrick

6
如果您想从不同于a的其他地方访问它,该Component怎么办?例如,在redux动作内部。
Maximus S

73
有时我想知道为什么仅从一个链接移到另一个链接会如此复杂=))
Thai Tran

12
有人会认为,在当今时代,重定向不会那么复杂。
JohnAllen

Answers:


413

如果您定位到浏览器环境,则需要使用react-router-dompackage而不是react-router。他们按照相同的方法作出反应那样,以核心分开,( react)和特定于平台的代码,( react-domreact-native用细微的差别,你并不需要安装两个独立的包),这样的环境包包含一切你需要。您可以通过以下方式将其添加到您的项目中:

yarn add react-router-dom

要么

npm i react-router-dom

您需要做的第一件事就是<BrowserRouter>在应用程序中提供一个作为最顶层的父组件。<BrowserRouter>使用HTML5 historyAPI并为您管理它,因此您不必担心自己实例化并将其<BrowserRouter>作为道具传递给组件(就像在以前的版本中一样)。

在V4中,要以编程方式进行导航,您需要访问history对象(可通过React 进行访问)context,只要您将<BrowserRouter> 提供程序组件作为应用程序中最顶层的父对象即可。该库通过上下文公开router本身包含history为属性的对象。该history接口提供多种导航方法,比如pushreplacegoBack,等等。您可以在此处检查属性和方法的整个列表。

给Redux / Mobx用户的重要说明

如果您将redux或mobx用作应用程序中的状态管理库,则可能遇到了组件问题,这些组件应该是位置感知的,但在触发URL更新后不会重新呈现

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

connect和observer都创建了组件,这些组件的shouldComponentUpdate方法对当前道具和下一个道具进行了浅层比较。只有更改了至少一个道具后,这些组件才会重新渲染。这意味着,为了确保它们在位置更改时更新,需要为它们提供一个在位置更改时更改的道具。

解决此问题的2种方法是:

  • 连接的组件包裹在无路可走的地方<Route />。当前location对象是a <Route>传递给它渲染的组件的道具之一
  • 连接的组件与withRouter高阶组件包装在一起,实际上其效果和注入location与道具相同

抛开这些,有四种编程方式进行导航,并按建议进行排序:

1.-使用<Route>组件

它提倡一种声明式风格。在v4之前,<Route />组件被放置在组件层次结构的顶部,必须事先考虑您的路由结构。但是,现在您可以在树中的任何位置拥有<Route>组件,从而使您可以更好地控制根据URL进行有条件的呈现。向组件注入,并作为道具。导航方法(如,,...)都可以作为属性对象。RoutematchlocationhistorypushreplacegoBackhistory

有3种方法可以Route通过使用componentrenderchildren道具来用a渲染某些事物,但在同一内不要使用多个Route。选项取决于用例,但是基本上前两个选项仅在path匹配url位置时才呈现组件,而children无论路径是否与位置匹配,都将呈现组件(可用于基于URL调整UI匹配)。

如果要自定义组件渲染输出,则需要将组件包装在函数中并使用render选项,以便将所需的其他道具matchlocation和除外)传递给组件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.-使用withRouterHoC

这个更高阶的成分将注入与相同的道具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组件

渲染<Redirect>会导航到新位置。但是请记住,默认情况下,当前位置会被新位置替换,例如服务器端重定向(HTTP 3xx)。新位置由toprop 提供,可以是字符串(重定向到的URL)或location对象。如果您想将新条目推送到历史记录中,则还传递一个push道具并将其设置为true

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

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

由于上下文仍然是一个实验性的API,因此有点气our ,它可能会在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,
  }),
};

不用说,还有其他非路由器生态系统专用的Router组件,例如在内存<NativeRouter>中复制导航堆栈并定位到React Native平台(可通过软件包获得)。react-router-native

有关更多参考,请随时查看官方文档。该库的一位共同作者还制作了一个视频,其中对React-router v4进行了非常酷的介绍,重点介绍了一些主要更改。


2
我正在使用V4,上面的工作正常。我花了相当多的时间来研究V4路由器,因为似乎有一些奇怪的选择,但是上述方法肯定可以工作。我假设您是withRouterreact-router-dom
Sam Parmenter

3
我是withRouter从导入的react-router-dom。history.push确实会更改网址,但<Route>直到我强制刷新页面后,它似乎才加载...
BreakDS

1
@rauliyohmc我错了。问题是我有<Router> 一个装饰有React的组件@observer,这触发了这个问题。解决方法是@withRouter在每个这样的React组件上使用。
BreakDS

3
对于那些遇到这个好答案的人来说,withRouter只是在后台使用HOC的HOC Route。这意味着它仅使用3个道具,历史记录,比赛和位置。在上面的示例中,似乎pushwithRouter将添加到的道具ButtonToNavigate,但事实并非如此。props.history.push将不得不使用。希望这对其他感到困惑的人有所帮助。
Vinnymac

39
哇。browserHistory.push('/path/some/where')似乎要简单得多。作者试图阻止命令式编程,但有时效果更好!
勒克斯

149

完成它的最简单方法是:

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

注意:

  • 您可能希望将“ history prop父”组件向下传递到要调用该操作的组件(如果该组件不可用)。

10
我使用4.0路由器,但道具上没有历史记录键。我该如何解决?
Nicolas S.Xu

41
如果您this.props.history的组件中没有可用的组件,则可以import {withRouter} from 'react-router-dom'然后 export default withRouter(MyComponent)(或const MyComponent = withRouter(...))将其插入history组件的道具中。
Malvineous

@Malvineous多数民众赞成在有趣,不知道这一点!会尝试的!
Jikku Jose

2
我必须缺少一些基本知识,因为这对我来说非常有用,所以我不知道为什么所有答案都需要如此冗长的解释。
约书亚·品特

如何防止重新安装组件history.push并仅触发更新(例如单击)<Link>
Rahul Yadav,

55

迁移到React-Router v4时,我遇到了类似的问题,因此我将在下面解释我的解决方案。

请不要认为此答案是解决问题的正确方法,我想随着React Router v4变得更加成熟并退出beta版,很有可能会出现更好的情况(它甚至可能已经存在,而我只是没有发现) 。

对于上下文,我遇到了这个问题,因为我偶尔会Redux-Saga以编程方式更改历史记录对象(例如,用户成功进行身份验证时)。

在React Router文档中,查看<Router> 组件,您可以看到您有能力通过道具传递自己的历史对象。这是该方案的本质- 我们提供的历史对象,以React-Router全球模块。

脚步:

  1. 安装历史记录npm模块- yarn add history npm install history --save
  2. history.js在您的App.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. 调整URL就像通过导入之前,你的世界历史的对象:

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

现在所有内容都应该保持同步,并且您还可以通过编程方式而不是通过组件/容器来访问设置历史对象的方法。


5
此更改对我有用,但这仅是因为我正在组件外部导航。如果要在OP之类的组件中导航,我将使用@rauliyohmc建议的方法,即使用Route组件传递的道具。
丹·埃利斯

2
从08/17开始,这是推荐的方法
Spets

2
@Spets在我的情况下,如果我使用这种方法,则在推送后将正确更新链接,但不会正确呈现组件(例如,更新链接后,除非您强制页面刷新,否则不会更新组件)。您在哪里找到推荐的方法?任何链接/来源?
很酷的

2
@ScottCoates我使用上面的示例进行了整理,实际上是通过提供历史记录作为参数,但是在我自己调试了节点模块之后。使用“ BrowserHistory作为路由器”的导入在整个网络中犯了一个普遍的错误,而在最新版本的react-router-dom中,存在另一个称为路由器的对象。将其与上面示例中创建的历史结合使用,效果很好。
很酷的

2
网址已更新,但页面未基于新的根呈现。有什么办法吗?为什么触发路线如此困难?设计做出反应的人不在他们的脑海中?
Nicolas S.Xu

43

TL; DR:

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

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

push:布尔值-如果为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 +属性初始化程序来初始化状态。如果您有兴趣,也请在这里查看。


5
这应该被接受。最简单的优雅解决方案!为@lustoykov +1
阿里·阿巴斯·

1
我还将在componentDidUpdate中将导航设置回false,因为我的按钮位于标题中,否则只能导航一次。
peterorum's

仅当您知道页面加载时的重定向时,此方法才有效。如果您正在等待异步调用返回(即通过Google或其他方式进行身份验证),则必须使用其中一种history.push()方法。
brittohalloran

并非如此,您仍然可以将react的声明性与<Redirect />组件结合使用。如果该页面未加载,则可以回
退到

@brittohalloran使用适当的react路由器的方法的想法是确保使用setState强制重新渲染。
特别的人

16

useHistory如果使用功能组件,请使用钩子

您可以使用useHistory钩子来获取history实例。

import { useHistory } from "react-router-dom";

const MyComponent = () => {
  var history = useHistory();

  return (
    <button onClick={() => history.push("/about")}>
      Click me
    </button>
  );
}

useHistory挂钩使您可以访问可用于导航的历史记录实例。

history在页面组件内使用属性

React Router注入了一些属性,包括history到页面组件。

class HomePage extends React.Component {
  render() {
    const { history } = this.props;

    return (
      <div>
        <button onClick={() => history.push("/projects")}>
          Projects
        </button>
      </div>
    );
  }
}

包装子组件withRouter以注入路由器属性

withRouter包装器将路由器属性注入组件。例如,您可以使用此包装器注入路由器以注销放置在用户菜单内的按钮组件。

import { withRouter } from "react-router";

const LogoutButton = withRouter(({ history }) => {
  return (
    <button onClick={() => history.push("/login")}>
      Logout
    </button>
  );
});

export default LogoutButton;

11

您还可以简单地使用道具来访问历史对象: this.props.history.push('new_url')


5
仅在组件直接从路由器继承时才有用。以免将历史记录支持传递给需要此功能的每个组件
。– mwieczorek

3
如果您this.props.history的组件中没有可用的组件,则可以import {withRouter} from 'react-router-dom'然后 export default withRouter(MyComponent)(或const MyComponent = withRouter(...))将其插入history组件的道具中。
Malvineous

9

步骤1:只有一件事要导入:

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

步骤2:在您的路线中,传递历史记录:

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

步骤3:历史记录在下一个组件中作为道具的一部分被接受,因此您可以简单地:

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

这很简单,而且功能强大。


7

这有效:

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

const SomeComponent = withRouter(({ history }) => (
    <div onClick={() => history.push('/path/some/where')}>
        some clickable element
    </div>); 
);

export default SomeComponent;

5

我的答案类似于亚历克斯的。我不确定为什么React-Router会如此复杂。为什么我必须用HoC包装我的组件才能访问本质上是全局的?

无论如何,如果您看一下它们的实现方式<BrowserRouter>,那只是历史的一小部分包装。

我们可以提取历史记录,以便可以从任何地方导入它。但是,诀窍在于,如果您正在执行服务器端渲染,并且尝试使用import“历史记录”模块,则它将无法正常工作,因为它使用的是仅限浏览器的API。但这没关系,因为我们通常只响应点击或其他客户端事件进行重定向。因此,可以假冒它是可以的:

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

在webpack的帮助下,我们可以定义一些变量,以便我们知道所处的环境:

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将不起作用,因为它仅适用于顶层。


6
该死的,他们使事情变得如此复杂。
Abhishek

4
我完全同意你的看法。仅导航便是如此复杂
Thai Tran

5

我认为@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工作...

尚不清楚为什么以前没有人提供此解决方案。希望对您有所帮助,如果您发现任何问题,请告诉我。


4

我已经测试了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>
             )
         }

在clickhandler()中

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

希望能帮助到你。让我知道。


这种方法有什么陷阱吗?在我的项目中,只有当我在构造函数中设置状态时,才能从那里重定向到我想要的任何页面。但是,当我在事件上设置状态(例如,道具确实发生了变化)时,我看到了以新状态调用的render方法,但是没有发生重定向,我看到的是同一页面
Dmitry Malugin

陷阱是它将取代历史,因此您将无法回击-因此,基本上,这不是一个好的解决方案。
dannytenaglias

4

我为此苦了一段时间-这么简单,却又如此复杂,因为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>

我发现此解决方案非常有用。我正在复制代码。让我知道您将其放在github上-我直接将其归因于您。
Abhinav Manchanda

3

由于没有其他方法可以处理这种可怕的设计,因此我编写了一个使用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>

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

如果您没有在组件中找到this.props.history,请尝试以下操作

import {withRouter} from 'react-router-dom'
export default withRouter(MyComponent)  

非常感谢!
QauseenMZ

1

有时我更喜欢先按应用程序再按按钮来切换路由,这是一个对我有用的最小工作示例:

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

0

对于需要在完全初始化路由器之前使用React RouterReact Router Dom进行重定向的人员,您可以通过简单地访问history对象并将新状态推入到您的结构中来提供重定向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>)
    }


}

在这里,我们想通过在组件渲染之前与历史对象进行交互来更改依赖于子域的输出Routes,我们可以有效地重定向,同时仍然保持路由完好无损。

window.history.pushState('', '', './login');
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.