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

Col*_*amp 313 ecmascript-6 reactjs react-router-v4

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

import { browserHistory } from 'react-router'
browserHistory.push('/path/some/where')
Run Code Online (Sandbox Code Playgroud)

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

rgo*_*ezz 400

如果您的目标是浏览器环境,则需要使用react-router-dompackage而不是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> routerhistoryhistorypushreplacegoBack

Redux/Mobx用户的重要说明

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

这种情况正在发生,因为使用上下文模型react-router传递location给组件.

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

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

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

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

1.-使用<Route>组件

它促进了一种陈述式的风格.在v4之前,<Route />组件放置在组件层次结构的顶部,必须事先考虑您的路径结构.但是,现在您可以在树中的任何位置放置<Route>组件,从而可以根据URL对条件渲染进行更精细的控制.注入,并作为组件的道具.导航方法(如,,...)都可以作为属性对象.RoutematchlocationhistorypushreplacegoBackhistory

有3种方式来呈现一个东西Route,通过使用component,renderchildren道具,但不要使用超过一个在同一个Route.选择取决于用例,但基本上前两个选项仅在path匹配url位置时呈现组件,而children组件将呈现路径是否与位置匹配(对于基于URL调整UI有用)匹配).

如果您想自定义您的组件渲染输出,你需要用你的组件在功能和使用render选项,以便传递给您的组件,你的愿望,除了任何其他的道具match,locationhistory.举例说明:

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>
);
Run Code Online (Sandbox Code Playgroud)

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);
Run Code Online (Sandbox Code Playgroud)

3.-使用Redirect组件

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

<Redirect to="/your-new-location" push />
Run Code Online (Sandbox Code Playgroud)

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,
  }),
};
Run Code Online (Sandbox Code Playgroud)

毋庸置疑,还有其他路由器组件适用于非浏览器生态系统,例如在内存<NativeRouter>中复制导航堆栈并以React Native平台为目标,可通过包获得.react-router-native

如有任何进一步的参考,请不要犹豫,看看官方文档.还有一个由该库的共同作者制作的视频,它为react-router v4提供了很酷的介绍,突出了一些重大变化.

  • 哇.`browserHistory.push('/ path/some/where')`似乎更简单了.作者试图阻止命令式编程,但有时候它会更好! (36认同)
  • 我从`react-router-dom`导入`withRouter`.history.push确实改变了url,但是在我强制刷新页面之前似乎没有加载`<Route>` (3认同)
  • 对于那些遇到这个伟大答案的人来说,"withRouter"只是一个在引擎盖下使用"Route"的HOC.这意味着它只使用[3道具,历史,匹配和位置](https://reacttraining.com/react-router/web/api/Route/Route-props).在上面的例子中,似乎`push`是`withRouter`将添加到`ButtonToNavigate`的道具,但事实并非如此.必须使用`props.history.push`.希望这能帮助那些有点困惑的人. (3认同)
  • 我正在使用V4,以上工作正常.我花了相当多的时间来讨论V4路由器,因为看起来有些奇怪的选择,但上面肯定有效.我假设你从`react-router-dom`导入`withRouter` (2认同)

Jik*_*ose 143

完成任务的最简单方法:

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

注意:

  • history prop如果不可用,您可能希望将父组件向下传递给要调用操作的组件.

  • 如果你的组件中没有`this.props.history`,那么你可以从'react-router-dom'`导入{withRouter}然后``export default withRouter(MyComponent)`(或`const MyComponent = withRouter(...)`)它将在你的组件的道具中插入`history`项. (39认同)
  • 我使用4.0路由器,但道具上没有历史记录密钥.我该如何解决? (10认同)
  • 我必须缺少一些基本知识,因为这对我来说非常有用,所以我不知道为什么所有答案都需要如此冗长的解释。 (2认同)

Ale*_*ann 55

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

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

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

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

脚步:

  1. 安装历史记录npm模块 - yarn add history npm install history --save
  2. 创建一个history.jsApp.jslevel文件夹中调用的文件(这是我的偏好)

    // src/history.js
    
    import createHistory from 'history/createBrowserHistory';
    export default createHistory();`
    
    Run Code Online (Sandbox Code Playgroud)
  3. 将此历史记录对象添加到您的路由器组件中

    // src/App.js
    
    import history from '../your/path/to/history.js;'
    <Router history={history}>
    // Route tags here
    </Router>
    
    Run Code Online (Sandbox Code Playgroud)
  4. 通过导入调整URL就像之前你的世界历史的对象:

    import history from '../your/path/to/history.js;'
    history.push('new/path/here/');
    
    Run Code Online (Sandbox Code Playgroud)

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

  • 这个改变对我有用,但这只是因为我在一个组件之外导航.如果我在像OP这样的组件内导航,我会使用@rauliyohmc建议的方法,使用由`Route`组件传递的props. (5认同)
  • 这是08/17的推荐方法 (2认同)
  • @Spets在我的情况下,如果我使用这种方法,链接将在推送后正确更新,但组件未正确呈现(例如,在更新链接之后,组件不会更新,除非您强制页面刷新).你在哪里发现这是推荐的方法?任何链接/来源? (2认同)
  • @ScottCoates我使用上面的示例进行了整理,实际上是通过提供历史记录作为参数,但是在我自己调试了节点模块之后。使用“ BrowserHistory作为路由器”的导入在整个网络中犯了一个普遍的错误,而在最新版本的react-router-dom中却存在另一个称为路由器的对象。结合使用上面的示例中创建的历史记录可以很好地工作。 (2认同)
  • url已更新,但页面未基于新根进行渲染.有解决方案吗 为什么触发路线如此困难?设计反应的人不在乎他们的想法? (2认同)
  • 同样,位置栏正在更新到新路径,但不会呈现任何内容. (2认同)

Lyu*_*mir 39

TL; DR:

if (navigate) {
  return <Redirect to="/" push={true} />
}
Run Code Online (Sandbox Code Playgroud)

简单而陈述的答案是你需要<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>
    )
  }
}
Run Code Online (Sandbox Code Playgroud)

完整的例子在这里.在这里阅读更多.

PS.该示例使用ES7 +属性初始化程序初始化状态.如果你有兴趣,也可以看看这里.

  • 这应该是接受的答案.最简单优雅的解决方案 给@lustoykov +1 (5认同)

Mis*_*rov 36

useHistory如果您使用函数组件,请使用钩子

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

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

const MyComponent = () => {
  const history = useHistory();
  
  return (
    <button onClick={() => history.push("/about")}>
      Click me
    </button>
  );
}
Run Code Online (Sandbox Code Playgroud)

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>
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

import { withRouter } from "react-router";

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

export default LogoutButton;
Run Code Online (Sandbox Code Playgroud)


use*_*685 8

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

  • 仅在直接来自路由器的组件中有用.为了将历史道具传递给您需要此功能的每个组件. (5认同)
  • 如果您的组件中没有“ this.props.history”,则可以从“ react-router-dom”中导入{withRouter},然后“使用Router(MyComponent)导出默认值”(或“ const MyComponent = withRouter(...)`),它将在组件的props中插入`history`项目。 (2认同)

小智 8

第1步:在顶部只导入一件事:

    import {Route} from 'react-router-dom';
Run Code Online (Sandbox Code Playgroud)

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

    <Route exact path='/posts/add' render={({history})  => (
      <PostAdd
        history={history}
      />
    .)}/>
Run Code Online (Sandbox Code Playgroud)

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

    this.props.history.push('/');
Run Code Online (Sandbox Code Playgroud)

这很容易而且非常强大.


Kay*_*ast 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;
Run Code Online (Sandbox Code Playgroud)


小智 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>
  );
}
Run Code Online (Sandbox Code Playgroud)


jar*_*m1r 5

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

我也有同样的问题,我发现像下面这样处理它效果最好(甚至可能是它的意图)。它使用状态、三元运算符和<Redirect>

在构造函数()中

this.state = {
    redirectTo: null
} 
this.clickhandler = this.clickhandler.bind(this);
Run Code Online (Sandbox Code Playgroud)

在渲染()中

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

在点击处理程序()中

 this.setState({ redirectTo: '/path/some/where' });
Run Code Online (Sandbox Code Playgroud)

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


mpe*_*pen 5

我的答案与Alex的相似.我不确定为什么React-Router这样做不必要地复杂化.为什么我必须用HoC包装我的组件才能访问本质上是全局的?

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

我们可以将该历史记录拉出来,以便我们可以从任何地方导入它.但是,诀窍在于,如果您正在进行服务器端渲染并尝试import使用历史记录模块,那么它将无法工作,因为它使用仅浏览器的API.但这没关系,因为我们通常只会重定向以响应点击或其他一些客户端事件.因此伪造它可能是可以的:

// history.js
if(__SERVER__) {
    module.exports = {};
} else {
    module.exports = require('history').createBrowserHistory();
}
Run Code Online (Sandbox Code Playgroud)

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

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

现在你可以

import history from './history';
Run Code Online (Sandbox Code Playgroud)

从任何地方.它只会在服务器上返回一个空模块.

如果你不想使用这些魔法变量,你只需要require在需要它的全局对象中(在你的事件处理程序中).import不起作用,因为它只适用于顶层.

  • 该死的他们让它如此复杂. (6认同)
  • 我完全赞同你.这对于导航而言非常复杂 (4认同)

mwi*_*rek 5

我为此苦苦挣扎了一段时间——事情如此简单,但又如此复杂,因为 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
};
Run Code Online (Sandbox Code Playgroud)

然后将其添加到您的render()方法中:

<LinkButton className="btn btn-primary" to="/location">
    Button Text
</LinkButton>
Run Code Online (Sandbox Code Playgroud)


use*_*247 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");
Run Code Online (Sandbox Code Playgroud)

这使我可以编写带有操作/调用的简单服务,可以调用该操作/调用以从所需的任何组件进行导航,而无需在组件上进行大量的HoC工作...

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