ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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

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

b
basse

如果您以浏览器环境为目标,则需要使用 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 进行浅比较。这些组件只会在至少一个道具发生变化时重新渲染。这意味着为了确保它们在位置更改时更新,需要为它们提供一个在位置更改时更改的道具。

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

将连接的组件包装在无路径的 中。当前位置对象是 传递给它呈现的组件的道具之一

用 withRouter 高阶组件包装连接的组件,实际上具有相同的效果并将位置作为道具注入

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

1.- 使用 组件

<路线/>

<路线>

任何地方

路线

匹配

地点

历史

代替

回去

历史

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

如果您想自定义组件渲染输出,您需要将组件包装在一个函数中并使用 render 选项,以便将您想要的任何其他道具传递给您的组件,除了 { 2}、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>
);

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 to="/your-new-location" push />

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

语境

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 的非常酷的介绍,突出了一些主要变化。


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

完成它的最简单方法:

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

笔记:

您可能希望将历史道具从父组件传递到要调用该操作的组件(如果它不可用)。


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

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

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

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

在 React Router 文档中,查看 <Router> component,您可以看到您可以通过道具传递您自己的历史记录对象。这是解决方案的精髓 - 我们从 global 模块向 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;' // 在这里路由标签 像以前一样通过导入全局历史对象来调整 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 已更新,但页面未基于新根呈现。有什么解决办法吗?为什么触发路线这么难?设计反应的人疯了吗?
L
Lyubomir

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

完整示例 here。阅读更多here

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


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

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

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

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

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

useHistory 挂钩可让您访问可用于导航的历史实例。

在页面组件中使用历史属性

React Router 会向页面组件注入一些属性,包括 history

class HomePage extends React.Component {
  render() {
    const { history } = this.props;

    return (
      <div>
        <button onClick={() => history.push("/projects")}>
          Projects
        </button>
      </div>
    );
  }
}

用Router包裹子组件以注入路由器属性

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

import { withRouter } from "react-router";

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

export default LogoutButton;

T
TheMisir

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


仅在直接从路由器继承的组件中才有用。以免您将 history 属性传递给您需要此功能的每个组件。
如果您的组件中没有可用的 this.props.history,那么您可以先import {withRouter} from 'react-router-dom',然后是 export default withRouter(MyComponent)(或 const MyComponent = withRouter(...)),它会在组件的 props 中插入 history 项。
T
TheMisir

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

这很容易而且非常强大。


m
mpen

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

无论如何,如果您看看他们是如何实现 <BrowserRouter> 的,它只是围绕 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';

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

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


该死的,他们把事情搞得这么复杂。
我完全同意你的看法。这对于导航来说太复杂了
佚名

我认为@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,也许这就是问题所在。
佚名

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

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

K
Kaya Toast

这有效:

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

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

export default SomeComponent;

j
jar0m1r

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

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


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

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

由于没有其他方法可以处理这种可怕的设计,我编写了一个使用 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>

S
Shashwat Gupta
this.props.history.push("/url")

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

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

p
piotr_cz

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

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

l
li x

对于那些需要在使用 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');