如何在React Router v4中推送到历史记录?


360

在当前版本的React Router(v3)中,我可以接受服务器响应并使用 browserHistory.push转到相应的响应页面。但是,这在v4中不可用,我不确定哪种适当的处理方式。

在此示例中,使用Redux,当用户提交表单时,components / app-product-form.js调用this.props.addProduct(props)。服务器返回成功后,该用户将被带到“购物车”页面。

// actions/index.js
export function addProduct(props) {
  return dispatch =>
    axios.post(`${ROOT_URL}/cart`, props, config)
      .then(response => {
        dispatch({ type: types.AUTH_USER });
        localStorage.setItem('token', response.data.token);
        browserHistory.push('/cart'); // no longer in React Router V4
      });
}

如何从React Router v4的功能重定向到购物车页面?


只是从最后提供的解决方案以及GitHub上React Router问题中的建议中补充这一点,使用context手动传递您需要的内容是“不可行的”。除非我是图书馆作者,否则就不需要使用它。实际上,Facebook建议不要这样做。
克里斯(Chris

@Chris您是否找到了解决方案?我需要推动行动的另一个组成部分,就像您在此处所解释的一样
-G先生

@G先生,可惜我没有。最后我读的是React培训团队,该团队维护React Router的redux软件包。我没有让它成功的运气,而且他们也没有为解决它付出很多工作。
克里斯(Chris

为什么我们不能使用windows.location.href = URL?使用它来更改URL和重定向是否有问题?

我不明白为什么不这样,但是使用的选项history也可以作为React Native的选项以及支持旧版浏览器的附加选项。
克里斯,

Answers:


308

您可以使用 history组件外部方法。通过以下方式尝试。

首先,history使用历史记录包创建一个对象:

// src/history.js

import { createBrowserHistory } from 'history';

export default createBrowserHistory();

然后将其包装<Router>请注意,您应该使用import { Router }而不是import { BrowserRouter as Router }):

// src/index.jsx

// ...
import { Router, Route, Link } from 'react-router-dom';
import history from './history';

ReactDOM.render(
  <Provider store={store}>
    <Router history={history}>
      <div>
        <ul>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/login">Login</Link></li>
        </ul>
        <Route exact path="/" component={HomePage} />
        <Route path="/login" component={LoginPage} />
      </div>
    </Router>
  </Provider>,
  document.getElementById('root'),
);

从任何地方更改您的当前位置,例如:

// src/actions/userActionCreators.js

// ...
import history from '../history';

export function login(credentials) {
  return function (dispatch) {
    return loginRemotely(credentials)
      .then((response) => {
        // ...
        history.push('/');
      });
  };
}

UPD:您还可以在React Router FAQ中看到一个稍微不同的示例。


24
我尝试完全按照@OlegBelostotsky所说的去做,但是之后history.push('some path'),URL改变了,但是页面没有改变。我必须将其放在window.location.reload()代码的某些部分中才能使其正常工作。但是,在某些情况下,我必须保留redux状态树,并且重新加载会破坏它。还有其他解决方案吗?
sdabrutas

2
@idunno尝试使用withRouter高阶组件。
Oleg Belostotsky '18

这引发了一个错误,指出:createBrowserHistory不是函数。我能做什么?
AKJ

@AKJ也许import createHistory from 'history/createBrowserHistory'; export default createHistory();会工作。
Oleg Belostotsky

1
对不起downvote :)。尽管这也应该起作用,但是处理此问题的正确方法是克里斯的回答:stackoverflow.com/a/42716055/491075
gion_13

340

React Router v4与v3(及更早版本)有根本不同,您不能 browserHistory.push()像。

如果您需要更多信息,则此讨论似乎相关:

  • 创建一个新的browserHistory将不起作用,因为<BrowserRouter>会创建自己的历史实例,并监听此实例的更改。因此,其他实例将更改网址,但不会更新<BrowserRouter>
  • browserHistory 在v4中,react-router不公开,仅在v2中。

取而代之的是,您有几种选择可以执行此操作:

  • 使用 withRouter高阶成分

    相反,您应该使用withRouter高阶组件,并将其包装到将推送到历史记录的组件中。例如:

    import React from "react";
    import { withRouter } from "react-router-dom";
    
    class MyComponent extends React.Component {
      ...
      myFunction() {
        this.props.history.push("/some/Path");
      }
      ...
    }
    export default withRouter(MyComponent);

    查看官方文档以获取更多信息:

    您可以通过withRouter高阶组件访问history对象的属性和最接近<Route>match。每当路线更改时,withRouter都会使用与<Route>render props 相同的props重新渲染其组件{ match, location, history }


  • 使用contextAPI

    使用上下文可能是最简单的解决方案之一,但是作为实验性API,它是不稳定且不受支持的。仅在其他所有操作失败时才使用它。这是一个例子:

    import React from "react";
    import PropTypes from "prop-types";
    
    class MyComponent extends React.Component {
      static contextTypes = {
        router: PropTypes.object
      }
      constructor(props, context) {
         super(props, context);
      }
      ...
      myFunction() {
        this.context.router.history.push("/some/Path");
      }
      ...
    }

    看一下有关上下文的官方文档

    如果您希望应用程序稳定,请不要使用上下文。这是一个实验性的API,可能会在React的未来版本中中断。

    如果尽管这些警告仍坚持使用上下文,请尝试将上下文的使用隔离在较小的区域,并避免在可能的情况下直接使用上下文API,以便在API更改时更容易升级。


9
是的,我确实尝试过。感谢您的询问。:-)那么如何将上下文放入该动作函数中?到目前为止,它是不确定的。
克里斯(Chris

1
我已经研究了这个主题几天了,但还没有使它起作用。即使使用上面的示例,我也始终在上下文中定义路由器。我目前正在使用react v15.5.10,react-router-dom v4.1.1,prop-types 15.5.10。有关此文档的文档很少,而且不太清楚。
斯图

5
this.context.router.history.push('/path');
@Stu

1
@Chris的确,如果您尝试实例化历史对象并自己使用它,它将更改url,但不会呈现组件-如上所述。但是,如何在组件外部使用“ history.push”并强制组件渲染呢?
很酷,

52
这不能回答所问的问题,即如何访问history.push组件的外部。在组件外部时,不使用withRouter或上下文。
SunshinyDoyle

49

现在,通过react-router v5,您可以使用useHistory生命周期挂钩,如下所示:

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

function HomeButton() {
  let history = useHistory();

  function handleClick() {
    history.push("/home");
  }

  return (
    <button type="button" onClick={handleClick}>
      Go home
    </button>
  );
}

有关更多信息,访问:https : //reacttraining.com/react-router/web/api/Hooks/usehistory


1
这是非常受欢迎的更新!谢谢!
克里斯,

有什么我需要设置的特定方法吗,我在调用以下内容,let history = useHistory();但遇到Object is not callable错误,当我尝试查看console.log(useHistory)它的useHistory 显示为未定义时。使用"react-router-dom": "^5.0.1"
steff_bdh

@steff_bdh,您需要将其在package.json文件中更新为“ react-router-dom”:“ ^ 5.0.1”并运行“ npm install”
Hadi Abu

2
不错,但是不能在redux动作类中使用钩子,因为它们不是React组件/函数
Jay

使用(async)登录后如何将其用于重定向。这里的问题=> stackoverflow.com/questions/62154408/...
theairbend3r

29

React Router 4中最简单的方法是使用

this.props.history.push('/new/url');

但是要使用此方法,您现有的组件应有权访问history对象。我们可以通过访问

  1. 如果您的组件Route直接链接到,则您的组件已经可以访问history对象。

    例如:

    <Route path="/profile" component={ViewProfile}/>

    在这里ViewProfile可以访问history

  2. 如果没有Route直接连接。

    例如:

    <Route path="/users" render={() => <ViewUsers/>}

    然后,我们必须使用withRouter高阶函数来扭曲现有组件。

    内部 ViewUsers组件

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

    • export default withRouter(ViewUsers);

    就是这样,您的ViewUsers组件可以访问history对象了。

更新

2-在这种情况下,将所有路由传递props到您的组件,然后this.props.history即使没有HOC

例如:

<Route path="/users" render={props => <ViewUsers {...props} />}

1
优秀的!您的第二种方法也帮了我大忙,因为我的组件(需要访问this.props.history)来自HOC,这意味着它没有直接链接到Route,正如您所解释的。
cjauvin19年

25

这是我的方法:

import React, {Component} from 'react';

export default class Link extends Component {
    constructor(props) {
        super(props);
        this.onLogout = this.onLogout.bind(this);
    }
    onLogout() {
        this.props.history.push('/');
    }
    render() {
        return (
            <div>
                <h1>Your Links</h1>
                <button onClick={this.onLogout}>Logout</button>
            </div>
        );
    }
}

使用this.props.history.push('/cart');重定向到购物车页面将被保存在历史记录对象。

享受,迈克尔。


2
是的,看起来在组件内您可以很好地进行推送。影响组件外部导航的唯一方法是重定向。
克里斯(Chris)

14
这不能回答所问的问题,即如何访问history.push组件的外部。在组件外部时,不能使用this.props.history。
SunshinyDoyle

22

根据React Router v4文档-Redux深度集成会话

需要深度集成以:

“能够通过调度动作进行导航”

但是,他们建议此方法作为“深度集成”的替代方法:

“与其分发动作进行导航,不如传递提供的历史对象以将组件路由到您的动作并在那里进行导航。”

因此,您可以使用withRouter高阶组件包装组件:

export default withRouter(connect(null, { actionCreatorName })(ReactComponent));

它将历史API传递给道具。因此,您可以调用动作创建者,将历史作为参数进行传递。例如,在您的ReactComponent内部:

onClick={() => {
  this.props.actionCreatorName(
    this.props.history,
    otherParams
  );
}}

然后,在您的actions / index.js内部:

export function actionCreatorName(history, param) {
  return dispatch => {
    dispatch({
      type: SOME_ACTION,
      payload: param.data
    });
    history.push("/path");
  };
}

19

棘手的问题花了我很多时间,但最终,我以这种方式解决了这个问题:

将容器包装起来withRouter,并将历史记录传递给 mapDispatchToProps功能中的操作。实际上,使用history.push('/ url')进行导航。

行动:

export function saveData(history, data) {
  fetch.post('/save', data)
     .then((response) => {
       ...
       history.push('/url');
     })
};

容器:

import { withRouter } from 'react-router-dom';
...
const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    save: (data) => dispatch(saveData(ownProps.history, data))}
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Container));

这对React Router v4.x有效。


谢谢,您的withRouter解决方案适用于打字稿,但与以前的import { createBrowserHistory } from 'history' 任何想法相比,它的速度相当慢?
杰伊

7

this.context.history.push 不管用。

我设法使推像这样工作:

static contextTypes = {
    router: PropTypes.object
}

handleSubmit(e) {
    e.preventDefault();

    if (this.props.auth.success) {
        this.context.router.history.push("/some/Path")
    }

}

9
这不能回答所问的问题,即如何访问history.push组件的外部。在组件外部时,不能使用this.context。
SunshinyDoyle

6

在这种情况下,您会将道具传递给重击。所以你可以简单地打电话

props.history.push('/cart')

如果不是这种情况,您仍然可以通过组件传递历史记录

export function addProduct(data, history) {
  return dispatch => {
    axios.post('/url', data).then((response) => {
      dispatch({ type: types.AUTH_USER })
      history.push('/cart')
    })
  }
}

6

如果其他人有价值,我还会提供另一种解决方案。

我有一个history.js文件,其中包含以下内容:

import createHistory from 'history/createBrowserHistory'
const history = createHistory()
history.pushLater = (...args) => setImmediate(() => history.push(...args))
export default history

接下来,在定义路由器的根目录上,使用以下命令:

import history from '../history'
import { Provider } from 'react-redux'
import { Router, Route, Switch } from 'react-router-dom'

export default class Root extends React.Component {
  render() {
    return (
     <Provider store={store}>
      <Router history={history}>
       <Switch>
        ...
       </Switch>
      </Router>
     </Provider>
    )
   }
  }

最后,在我上,actions.js我导入了History并使用pushLater

import history from './history'
export const login = createAction(
...
history.pushLater({ pathname: PATH_REDIRECT_LOGIN })
...)

这样,我可以在API调用后继续执行新操作。

希望能帮助到你!


4

如果您使用的是Redux,那么我建议您使用npm软件包react-router-redux。它允许您调度Redux商店导航操作。

您必须按照其自述文件中的说明创建存储。

最简单的用例:

import { push } from 'react-router-redux'

this.props.dispatch(push('/second page'));

容器/组件的第二个用例:

容器:

import { connect } from 'react-redux';
import { push } from 'react-router-redux';

import Form from '../components/Form';

const mapDispatchToProps = dispatch => ({
  changeUrl: url => dispatch(push(url)),
});

export default connect(null, mapDispatchToProps)(Form);

零件:

import React, { Component } from 'react';
import PropTypes from 'prop-types';

export default class Form extends Component {
  handleClick = () => {
    this.props.changeUrl('/secondPage');
  };

  render() {
    return (
      <div>
        <button onClick={this.handleClick}/>
      </div>Readme file
    );
  }
}

1
除非您正在使用当前正在开发的版本,否则它不适用于react-router-redux next
froginvasion

4

我能够通过使用来完成此操作bind()。我想单击一个按钮index.jsx,将一些数据发布到服务器,评估响应,然后重定向到success.jsx。这就是我的解决方法...

index.jsx

import React, { Component } from "react"
import { postData } from "../../scripts/request"

class Main extends Component {
    constructor(props) {
        super(props)
        this.handleClick = this.handleClick.bind(this)
        this.postData = postData.bind(this)
    }

    handleClick() {
        const data = {
            "first_name": "Test",
            "last_name": "Guy",
            "email": "test@test.com"
        }

        this.postData("person", data)
    }

    render() {
        return (
            <div className="Main">
                <button onClick={this.handleClick}>Test Post</button>
            </div>
        )
    }
}

export default Main

request.js

import { post } from "./fetch"

export const postData = function(url, data) {
    // post is a fetch() in another script...
    post(url, data)
        .then((result) => {
            if (result.status === "ok") {
                this.props.history.push("/success")
            }
        })
}

success.jsx

import React from "react"

const Success = () => {
    return (
        <div className="Success">
            Hey cool, got it.
        </div>
    )
}

export default Success

因此,通过绑定thispostDatain index.jsx,我可以访问this.props.historyin request.js……然后可以在不同的组件中重用此功能,只需确保记得记得包含this.postData = postData.bind(this)在中constructor()


3

这是我的技巧(这是我的根目录文件,其中混有一些redux-尽管我没有使用react-router-redux):

const store = configureStore()
const customHistory = createBrowserHistory({
  basename: config.urlBasename || ''
})

ReactDOM.render(
  <Provider store={store}>
    <Router history={customHistory}>
      <Route component={({history}) => {
        window.appHistory = history
        return (
          <App />
        )
      }}/>
    </Router>
  </Provider>,
  document.getElementById('root')
)

然后,我可以window.appHistory.push()在任何需要的地方使用(例如,在我的redux存储库函数/ thunks / sagas等中),我曾经希望可以使用它,window.customHistory.push()但是由于某些原因react-router,即使URL更改了,它也似乎从未更新。但是这种方式我有EXACT实例react-router使用。我不喜欢将东西放到全球范围内,这是我要做的几件事之一。但这比我见过的IMO更好。


3

使用回调。它为我工作!

export function addProduct(props, callback) {
  return dispatch =>
    axios.post(`${ROOT_URL}/cart`, props, config)
    .then(response => {
    dispatch({ type: types.AUTH_USER });
    localStorage.setItem('token', response.data.token);
    callback();
  });
}

在组件中,您只需要添加回调

this.props.addProduct(props, () => this.props.history.push('/cart'))

3

因此,我的操作方式是:-而不是使用重定向history.push,我只使用Redirectfrom中的组件。react-router-dom 使用此组件时,您可以通过push=true,其余的将由它负责。

import * as React from 'react';
import { Redirect } from 'react-router-dom';
class Example extends React.Component {
  componentDidMount() {
    this.setState({
      redirectTo: '/test/path'
    });
  }

  render() {
    const { redirectTo } = this.state;

    return <Redirect to={{pathname: redirectTo}} push={true}/>
  }
}

这是正确的,不会破坏反应渲染周期
jzqa

1

现在,React路由器V4允许使用历史道具,如下所示:

this.props.history.push("/dummy",value)

然后,只要位置prop可用(state:{value}不是组件状态),就可以访问该值 。


1
这不能回答所问的问题,即如何访问history.push组件的外部。在组件外部时,不能使用this.props.history。
Arkady

0

您可以像这样使用它,因为我要进行登录并进行许多其他操作

class Login extends Component {
  constructor(props){
    super(props);
    this.login=this.login.bind(this)
  }


  login(){
this.props.history.push('/dashboard');
  }


render() {

    return (

   <div>
    <button onClick={this.login}>login</login>
    </div>

)


0

第一步将您的应用包装在路由器中

import { BrowserRouter as Router } from "react-router-dom";
ReactDOM.render(<Router><App /></Router>, document.getElementById('root'));

现在,我的整个应用程序都可以访问BrowserRouter。第二步,我导入Route,然后传递这些道具。可能在您的主文件之一中。

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

//lots of code here

//somewhere in my render function

    <Route
      exact
      path="/" //put what your file path is here
      render={props => (
      <div>
        <NameOfComponent
          {...props} //this will pass down your match, history, location objects
        />
      </div>
      )}
    />

现在,如果我在组件js文件中运行console.log(this.props),我应该得到如下所示的内容

{match: {…}, location: {…}, history: {…}, //other stuff }

步骤2我可以访问历史记录对象以更改我的位置

//lots of code here relating to my whatever request I just ran delete, put so on

this.props.history.push("/") // then put in whatever url you want to go to

而且我只是编码训练营的学生,所以我不是专家,但是我知道你也可以使用

window.location = "/" //wherever you want to go

如果我错了,请纠正我,但是当我进行测试时,它重新加载了整个页面,我认为这击败了使用React的整个观点。


0

创建Router自己的自定义browserHistory

import React from 'react';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';

export const history = createBrowserHistory();

const ExtBrowserRouter = ({children}) => (
  <Router history={history} >
  { children }
  </Router>
);

export default ExtBrowserRouter

接下来,在您的Root上定义您的Router,使用以下命令:

import React from 'react';       
import { /*BrowserRouter,*/ Route, Switch, Redirect } from 'react-router-dom';

//Use 'ExtBrowserRouter' instead of 'BrowserRouter'
import ExtBrowserRouter from './ExtBrowserRouter'; 
...

export default class Root extends React.Component {
  render() {
    return (
      <Provider store={store}>
        <ExtBrowserRouter>
          <Switch>
            ...
            <Route path="/login" component={Login}  />
            ...
          </Switch>
        </ExtBrowserRouter>
      </Provider>
    )
  }
}

最后,history在需要的地方导入并使用它:

import { history } from '../routers/ExtBrowserRouter';
...

export function logout(){
  clearTokens();      
  history.push('/login'); //WORKS AS EXPECTED!
  return Promise.reject('Refresh token has expired');
}

0

如果您想在将函数作为值传递给组件的prop时使用历史记录,可以使用react-router 4来简单地history<Route/>组件的render属性中分解prop ,然后使用history.push()

    <Route path='/create' render={({history}) => (
      <YourComponent
        YourProp={() => {
          this.YourClassMethod()
          history.push('/')
        }}>
      </YourComponent>
    )} />

注意:要使它起作用,您应该在根组件周围包裹React Router的BrowserRouter组件(例如,可能在index.js中)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.