377

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

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

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

4

17 回答 17

440

如果您的目标是浏览器环境,则需要使用react-router-dom包,而不是react-router. 它们遵循与 React 相同的方法,以分离核心 ( react) 和平台特定代码 ( react-dom, react-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 方法对其当前 props 和下一个 props 进行浅比较。这些组件只会在至少一个道具发生变化时重新渲染。这意味着为了确保它们在位置更改时更新,需要为它们提供一个在位置更改时更改的道具。

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

  • 将连接的组件包装在无路径的<Route />. 当前location对象是 a<Route>传递给它呈现的组件的道具之一
  • 用高阶组件包装连接的组件,实际上具有相同的效果并作为道具 注入withRouterlocation

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

1.- 使用<Route>组件

它提倡声明式风格。在 v4 之前,<Route />组件被放置在组件层次结构的顶部,必须事先考虑您的路由结构。但是,现在您可以在树中的任何位置<Route>拥有组件,从而允许您更好地控制根据 URL 有条件地呈现。将,和as props 注入到您的组件中。导航方法(例如, , ...)可用作对象的属性。RoutematchlocationhistorypushreplacegoBackhistory

有 3 种方法可以使用 、 、 或道具来渲染某些东西Routecomponentrender不要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.- 使用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组件

渲染 a<Redirect>将导航到一个新位置。但请记住,默认情况下,当前位置会被新位置替换,例如服务器端重定向 (HTTP 3xx)。新位置由toprop 提供,可以是字符串(要重定向到的 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包获得。

如需进一步参考,请随时查看官方文档。还有一个由该库的一位合著者制作的视频,提供了对 react-router v4 的非常酷的介绍,突出了一些主要变化。

于 2017-02-08T21:51:29.253 回答
166

完成它的最简单方法:

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

笔记:

  • history prop如果父组件不可用,您可能希望将其传递给要调用该操作的组件。
于 2017-05-23T10:30:41.603 回答
57

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

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

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

在 React Router 文档中,查看<Router> 组件,您可以看到您能够通过 prop 传递您自己的历史对象。这是解决方案的本质——我们从一个全局模块中提供历史对象。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/');
    

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

于 2017-02-18T20:19:42.787 回答
48

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

于 2017-04-10T21:41:54.707 回答
38

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

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

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

const MyComponent = () => {
  const 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;
于 2019-10-01T22:25:55.963 回答
11

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

于 2017-05-19T22:35:58.817 回答
9

第 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('/');

这很容易而且非常强大。

于 2017-09-29T15:11:03.827 回答
7

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

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>
  );
}
于 2021-01-19T09:35:04.490 回答
7

我的回答与Alex 的类似。我不确定为什么 React-Router 让这变得如此不必要的复杂。为什么我必须用 HoC 包装我的组件才能访问本质上是全局的?

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

我们可以提取历史记录,以便我们可以从任何地方导入它。然而,诀窍是,如果您正在执行服务器端渲染并尝试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不会起作用,因为它只在顶层起作用。

于 2017-06-14T16:08:30.013 回答
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;
于 2017-12-20T11:34:16.807 回答
6

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

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

于 2018-04-23T04:54:10.850 回答
4

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

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

于 2017-03-01T21:10:01.673 回答
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>
于 2017-09-21T14:52:17.960 回答
3
this.props.history.push("/url")

如果您还没有在组件中找到 this.props.history 可用,那么试试这个

import {withRouter} from 'react-router-dom'
export default withRouter(MyComponent)  
于 2019-12-29T11:39:16.880 回答
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>
于 2017-09-26T02:10:34.453 回答
2

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

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>
    )
  }
}
于 2017-06-13T18:47:05.927 回答
0

For those of you who require to redirect before fully initalizing a router using React Router or React Router Dom You can provide a redirect by simply accesing the history object and pushing a new state onto it within your constructur of app.js. Consider the following:

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


}

Here we want to change the output Routes dependant on a subdomain, by interacting with the history object before the component renders we can effectively redirect while still leaving our routes in tact.

window.history.pushState('', '', './login');
于 2019-01-11T20:11:10.980 回答