首页 文章

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

提问于
浏览
295

我刚刚从 v3 替换了react-router到 v4。
但我不知道如何以编程方式导航Component的成员函数。 i.e in handleClick() function 我想在处理一些数据后导航到/path/some/where。我曾经这样做过:

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

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

14 回答

  • 384

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

    yarn add react-router-dom

    要么

    npm i react-router-dom

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

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


    Redux/Mobx 用户的重要说明

    如果您在应用程序中使用 redux 或 mobx 作为状态管理库,则可能遇到了在触发 URL 更新后应该 location-aware 但不是 re-rendered 的组件的问题

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

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

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

    • 将您连接的组件包裹在无路径的<Route />中。当前location对象是<Route>传递给它呈现的组件的道具之一

    • withRouter higher-order 组件包裹你连接的组件,实际上具有相同的效果并注入location作为道具

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


    1.-使用<Route>组件

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

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

    如果要自定义组件渲染输出,则需要将组件包装在函数中并使用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>将导航到新位置。但请记住,默认情况下,当前位置被新的替换,如 server-side 重定向(HTTP 3xx)。新位置由to prop 提供,可以是字符串(重定向到的 URL)或location对象。如果你想将一个新条目推送到历史上,也要传递一个push prop 并将其设置为true

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

    4.-通过上下文手动访问路由器

    有点气馁,因为上下文仍然是一个实验性的 API,它很可能在未来的 React 版本中 break/change

    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包获得。

    如有任何进一步的参考,请不要犹豫,看看官方文件。还有一个由 co-authors 库创建的视频,它为 react-router v4 提供了很酷的介绍,突出了一些重大变化。

  • 136

    完成任务的最简单方法:

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

    注意:

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

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

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

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

    在 React Router 文档中,查看<Router> 零件,您可以看到您可以通过 prop 传递自己的历史对象。这是解决方案的本质 - 我们从全局模块向React-Router提供历史对象

    脚步:

    • 安装历史记录 npm 模块 - yarn add history npm install history --save

    • App.js级别文件夹中创建一个名为history.js的文件(这是我的偏好)

    // src/history.js
    
    import createHistory from 'history/createBrowserHistory';
    export default createHistory();`
    
    • 将此历史记录对象添加到您的路由器组件中
    // src/App.js
    
    import history from '../your/path/to/history.js;'
    <Router history={history}>
    // Route tags here
    </Router>
    
    • 通过导入您的全局历史记录对象,像以前一样调整 URL:
    import history from '../your/path/to/history.js;'
    history.push('new/path/here/');
    

    现在一切都应该保持同步,你也可以通过编程方式访问历史对象,而不是通过 component/container。

  • 36

    TL; DR:

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

    **简单和陈述性的答案是你需要将<Redirect to= push= />与 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 属性初始化程序初始化状态。如果您有兴趣,请查看这里

  • 8

    第 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;
    
  • 7

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

  • 5

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

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

    我们可以将该历史记录拉出来,以便我们可以从任何地方导入它。然而,诀窍在于,如果你正在进行 server-side 渲染并且你试图import历史模块,它将无法工作,因为它使用 browser-only API。但这没关系,因为我们通常只会重定向以响应点击或其他 client-side 事件。因此伪造它可能是可以的:

    // 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';
    

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

    如果你不想使用这些魔法变量,你只需要在需要它的全局对象中(在你的事件处理程序中)。 import将无效,因为它只适用于 top-level。

  • 4

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

    我也有同样的问题,我发现处理它像下面的工作最好(甚至可能是它的意图)。它使用 state,一个三元运算符和<Redirect>

    在 constructor()

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

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

  • 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>
    
  • 4

    我认为 that_1_covers 大多数情况减去一个我认为非常重要的情况。

    // 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");
    

    这允许我用 actions/calls 编写一个简单的服务,我可以调用它从我想要的任何组件进行导航而不需要在我的组件上做很多 HoC ...

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

  • 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>
    
  • 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完全初始化路由器之前重定向的人您可以通过简单地访问历史对象并在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');
    

相关问题