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

时间:2017-02-08 20:46:14

标签: reactjs ecmascript-6 react-router-v4

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

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

但是我无法在v4中找到这样的接口 如何使用v4进行导航?

17 个答案:

答案 0 :(得分:364)

如果您要定位浏览器环境,则需要使用react-router-dom包,而不是react-router。他们遵循与React相同的方法,以便将核心(react)和平台特定代码(react-domreact-native)与您所关注的细微差别区分开来#39;需要安装两个独立的软件包,因此环境软件包包含您需要的所有内容。您可以将其添加到项目中:

yarn add react-router-dom

npm i react-router-dom

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

在V4中,要以编程方式进行导航,只要您拥有history 提供商context对象,该对象可通过React <BrowserRouter>获取。 >组件作为应用程序中最顶层的父组件。该库通过上下文公开router对象,该对象本身包含history作为属性。 history界面提供了多种导航方法,例如pushreplacegoBack等。您可以查看属性和方法的完整列表here

Redux / Mobx用户的重要提示

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

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

  

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

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

  • 连接的组件包裹在无路径<Route />中。当前location对象是<Route>传递给其呈现的组件的道具之一
  • 使用withRouter高阶组件包裹已连接的组件,实际上具有相同的效果,并将location作为道具

除此之外,还有四种以编程方式导航的方法,按推荐排序:

1.-使用<Route>组件

它会提升声明式样式。在v4之前,<Route />组件放置在组件层次结构的顶部,必须事先考虑您的路径结构。但是,现在您可以在树中拥有<Route>组件任何位置,从而可以根据URL对条件渲染进行更精细的控制。 Routematchlocationhistory作为道具注入您的组件。导航方法(例如pushreplacegoBack ...)可用作history对象的属性。

使用Routecomponentrender道具,有3种方法可以使用children呈现内容,但不要使用多个在同一个Route。选择取决于用例,但基本上前两个选项只会在path与url位置匹配时呈现组件,而对于children,组件将呈现路径是否与位置匹配或不(用于根据URL匹配调整UI)。

如果要自定义组件呈现输出,则需要将组件包装在一个函数中并使用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,
  }),
};

export default withRouter(ButtonToNavigate);

3.-使用Redirect组件

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

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

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

有点气馁,因为context仍然是一个实验性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 Native平台,可用通过react-router-native包。

如需进一步参考,请不要犹豫,查看official docs。还有一个video由该库的共同作者提供,它为react-router v4提供了非常酷的介绍,突出了一些重大变化。

答案 1 :(得分:127)

完成任务的最简单方法:

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

注意:

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

答案 2 :(得分:51)

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

请不要将此答案视为解决问题的正确方法,我认为随着React Router v4变得更加成熟并且离开beta(它甚至可能已经存在而且我只是没发现它。)

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

在React Router文档中,查看<Router> component,您可以看到您可以通过道具传递自己的历史记录对象。这是解决方案的核心 - 我们从 全球 模块向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. 通过导入您的全局历史记录对象,像以前一样调整网址:

    import history from '../your/path/to/history.js;'
    history.push('new/path/here/');
    
  5. 现在所有内容都应保持同步,并且您还可以通过编程方式访问历史对象,而不是通过组件/容器。

答案 3 :(得分:34)

TL; DR:

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

完整示例here。 阅读更多here

PS。该示例使用ES7+ Property Initializers初始化状态。如果您有兴趣,请查看here

答案 4 :(得分:8)

第1步:最重要的是只能导入一件事:

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

第2步:在你的路线中,传递历史记录:

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

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

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

这很容易而且非常强大。

答案 5 :(得分:7)

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

答案 6 :(得分:6)

这有效:

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

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

export default SomeComponent;

答案 7 :(得分:5)

我的回答类似于Alex's。我不确定为什么React-Router这样做会如此不必要地复杂化。为什么我必须用HoC包装我的组件才能访问本质上是全局的?

无论如何,如果你看看他们如何实施<BrowserRouter>,它只是history周围的一个小包装。

我们可以将该历史记录拉出来,以便我们可以从任何地方导入它。但是,诀窍在于,如果您正在进行服务器端渲染并尝试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无法正常工作,因为它只适用于顶层。

答案 8 :(得分: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>

答案 9 :(得分:4)

您可以使用useHistory钩子来获取history实例,然后使用history.push('...')进行导航。

...
import {useHistory} from 'react-router-dom';

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

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

useHistory挂钩使您可以访问可用于导航的历史记录实例。
  ?https://reacttraining.com/react-router/web/api/Hooks/usehistory

答案 10 :(得分:4)

我现在已经测试了v4几天了......到目前为止我很喜欢它!一段时间后它才有意义。

我也有同样的问题,我发现处理它就像以下工作得最好(甚至可能是它的意图)。它使用state,一个三元运算符和<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' });

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

答案 11 :(得分: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>

答案 12 :(得分:3)

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

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

答案 13 :(得分:2)

您可以通过这种方式有条件地导航

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

答案 14 :(得分: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>
    )
  }
}

答案 15 :(得分:0)

对于那些需要使用React RouterReact 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>)
    }


}

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

window.history.pushState('', '', './login');

答案 16 :(得分:0)

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

如果您未在组件中找到this.props.history, 然后尝试

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