【问题标题】:Programmatically navigate using react router V4使用反应路由器 V4 以编程方式导航
【发布时间】:2017-06-26 16:03:59
【问题描述】:

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

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

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

【问题讨论】:

  • 可以通过props访问v4中的history对象:this.props.history.push('/')
  • 如果您想从与Component 不同的地方访问它怎么办?例如,在 redux 操作中。
  • 有时我想知道为什么从一个链接移动到另一个链接这么复杂 =))
  • 人们会认为在这个时代重定向不会那么复杂。
  • 我发现这篇文章很有帮助:medium.com/@anneeb/redirecting-in-react-4de5e517354a

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


【解决方案1】:

如果你的目标是浏览器环境,你需要使用react-router-dom 包,而不是react-router。他们遵循与 React 相同的方法,以便将核心 (react) 和平台特定代码 (react-dom, react-native) 分开,但您不需要安装两个单独的包,因此环境包包含您需要的一切。您可以将其添加到您的项目中:

yarn add react-router-dom

npm i react-router-dom

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

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

Redux/Mobx 用户重要提示

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

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

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

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

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

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

1.- 使用<Route> 组件

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

有 3 种方法可以使用 Route 渲染某些东西,使用 componentrenderchildren 道具,但不要在同一个 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 组件

渲染&lt;Redirect&gt; 将导航到新位置。但请记住,默认情况下,当前位置会被新位置替换,例如服务器端重定向 (HTTP 3xx)。新位置由 to 属性提供,它可以是字符串(重定向到的 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,
  }),
};

毋庸置疑,还有其他用于非浏览器生态系统的路由器组件,例如 &lt;NativeRouter&gt;,它复制了一个导航堆栈在内存中并以 React Native 平台为目标,可通过 @ 获得987654390@包。

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

【讨论】:

  • 我正在使用 V4,上面的工作正常。我花了相当多的时间来研究 V4 路由器,因为似乎有一些奇怪的选择,但上述方法确实有效。我假设您正在从 react-router-dom 导入 withRouter
  • 我正在从react-router-dom 导入withRouter。 history.push 确实更改了 url,但它似乎没有加载 &lt;Route&gt;,直到我强制刷新页面...
  • @rauliyohmc 我错了。问题是我在一个用@observer 装饰的React 组件中有&lt;Router&gt;,它触发this issue。解决方法是在每个这样的 React 组件上都有@withRouter
  • 对于那些遇到这个好答案的人来说,withRouter 只是一个在引擎盖下使用Route 的 HOC。这意味着它只使用3 props, history, match, and location。在上面的示例中,push 似乎是 withRouter 将添加到 ButtonToNavigate 的道具,但事实并非如此。 props.history.push 必须被使用。希望这可以帮助其他有点困惑的人。
  • 哇。 browserHistory.push('/path/some/where') 似乎要简单得多。作者试图阻止命令式编程,但有时它会更好地工作!
【解决方案2】:

最简单的方法:

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

注意:

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

【讨论】:

  • 我用的是4.0路由器,但是props上没有history key。我该如何解决?
  • 如果你的组件中没有this.props.history,那么你可以先import {withRouter} from 'react-router-dom',然后export default withRouter(MyComponent)(或const MyComponent = withRouter(...)),它会在你的组件中插入history道具。
  • @Malvineous 很有趣,不知道这个!会试试的!
  • 我一定遗漏了一些基本的东西,因为这对我来说非常有用,所以我不知道为什么所有的答案都需要如此冗长的解释。
  • 如何防止组件在history.push 上重新挂载并触发更新,就像我们点击&lt;Link&gt; 时一样
【解决方案3】:

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

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

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

在 React Router 文档中,查看 &lt;Router&gt; component ,您可以看到您可以通过道具传递自己的历史对象。这是解决方案的精髓 - 我们将历史对象从一个全局模块提供给React-Router

步骤:

  1. 安装历史 npm 模块 - yarn add history npm install history --save
  2. 在您的App.js 级别文件夹中创建一个名为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. 通过导入你的全局历史对象来调整 URL:

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

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

【讨论】:

  • 此更改对我有用,但只是因为我在组件之外导航。如果我在像 OP 这样的组件中导航,我会使用@rauliyohmc 建议的方法,即使用Route 组件传递的道具。
  • 这是 08/17 的推荐方法
  • @Spets 在我的情况下,如果我使用这种方法,推送后链接会正确更新,但组件没有正确呈现(例如,更新链接后,组件没有除非您强制刷新页面,否则更新)。您在哪里发现这是推荐的方法?任何链接/来源?
  • @ScottCoates 我使用上面的示例进行了整理,确实是通过提供历史记录作为参数,但是在我自己调试节点模块之后。使用“BrowserHistory as Router”的导入在整个网络上都犯了一个常见错误,而在最新版本的 react-router-dom 中存在另一个名为 Router 的对象。将它与上面示例中创建的历史结合使用就可以了。
  • url 更新了,但是页面没有基于新的根渲染。有什么解决办法吗?为什么触发路线这么难?设计反应的人疯了吗?
【解决方案4】:

TL;DR:

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

简单而明确的答案是您需要结合使用&lt;Redirect to={URL} push={boolean} /&gt;setState()

push: boolean - 当为真时,重定向会将新条目推送到历史记录中,而不是替换当前条目。


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

【讨论】:

  • 这应该是被接受的答案。最简单优雅的解决方案! +1 @lustoykov
  • 我还在 componentDidUpdate 中将导航设置为 false,因为我的按钮位于标题中,否则只会导航一次。
  • 这仅在您知道页面加载时重定向的情况下才有效。如果您正在等待异步调用返回(即通过 Google 进行身份验证或其他方式),那么您必须使用 history.push() 方法之一。
  • 并非如此,您仍然可以利用 react 的声明性特性与 组件相结合。如果页面没有加载,你可以回退到另一个
  • @brittohalloran 这个方法使用正确的反应路由器的想法是确保你使用 setState 来强制重新渲染。
【解决方案5】:

如果您使用函数组件,请使用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;

【讨论】:

    【解决方案6】:

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

    【讨论】:

    • 只有在直接从路由器继承的组件中才有用。以免您将 history 属性传递给您需要此功能的每个组件。
    • 如果你的组件中没有this.props.history,那么你可以先import {withRouter} from 'react-router-dom',然后export default withRouter(MyComponent)(或const MyComponent = withRouter(...)),它会在你的组件中插入history道具。
    【解决方案7】:

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

    这很简单,而且非常强大。

    【讨论】:

      【解决方案8】:

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

      无论如何,如果你看看他们是如何实现 &lt;BrowserRouter&gt; 的,它只是 history 的一个小包装。

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

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

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

      【讨论】:

      • 该死的,他们把事情搞得这么复杂。
      • 我完全同意你的看法。这对于一个导航来说太复杂了
      【解决方案9】:

      这行得通:

      import { withRouter } from 'react-router-dom';
      
      const SomeComponent = withRouter(({ history }) => (
          <div onClick={() => history.push('/path/some/where')}>
              some clickable element
          </div>); 
      );
      
      export default SomeComponent;
      

      【讨论】:

        【解决方案10】:

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

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

        【讨论】:

        • 喜欢这个想法,但我无法在路线更改时重新渲染任何东西。 (我使用@withRouter 来装饰任何依赖于路由的组件)。有什么想法吗?
        • 哦,我正在使用 v5,也许这就是问题所在。
        【解决方案11】:

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

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

        【讨论】:

          【解决方案12】:

          我已经测试 v4 几天了……到目前为止我很喜欢它!过了一会儿才有意义。

          我也有同样的问题,我发现像下面这样处理它效果最好(甚至可能是它的预期方式)。它使用状态、三元运算符和&lt;Redirect&gt;

          在构造函数中()

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

          希望对您有所帮助。告诉我。

          【讨论】:

          • 这种方法有什么陷阱吗?在我的项目中,只有当我在构造函数中设置状态时才有效,从那里我可以重定向到我想要的任何页面。但是当我在事件上设置状态时(例如,道具确实发生了变化),我看到使用新状态调用的渲染方法,但重定向不会发生我看到相同的页面
          • 陷阱是它将替换历史记录,因此您将无法反击 - 所以基本上,这不是一个好的解决方案。
          【解决方案13】:

          我为此苦苦挣扎了一段时间 - 事情如此简单,却又如此复杂,因为 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 上——我会直接把它归功于你。
          【解决方案14】:

          由于没有其他方法可以处理这种可怕的设计,我编写了一个使用withRouterHOC 方法的通用组件。下面的示例包装了一个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>
          

          【讨论】:

            【解决方案15】:
            this.props.history.push("/url")
            

            如果你还没有在你的组件中找到 this.props.history , 然后试试这个

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

            【讨论】:

              【解决方案16】:

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

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

              【讨论】:

                【解决方案17】:

                对于那些在使用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>)
                    }
                
                
                }
                

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

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

                【讨论】:

                  猜你喜欢
                  • 2017-09-27
                  • 2017-09-03
                  • 2020-01-05
                  • 1970-01-01
                  • 2018-09-26
                  • 2017-06-29
                  相关资源
                  最近更新 更多