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

时间:2017-03-09 16:56:12

标签: reactjs redux react-router react-router-v4

在当前版本的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的功能重定向到Cart页面?

24 个答案:

答案 0 :(得分:275)

React Router v4与v3(及更早版本)根本不同,你不能像以前一样browserHistory.push()

如果您想要更多信息,

This discussion似乎相关:

  
      
  • 创建一个新的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);
    

    查看official documentation了解详情:

      

    您可以通过withRouter高阶组件访问history对象的属性和最近的<Route>&#39; s match。 withRouter将在每次路径更改时使用与<Route>渲染道具相同的道具重新渲染其组件:{ match, location, history }

  • 使用context API

    使用上下文可能是最简单的解决方案之一,但作为实验性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");
      }
      ...
    }
    

    在上下文中查看official documentation

      

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

         

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

答案 1 :(得分:197)

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

首先,创建一个使用the history packagehistory对象:

// 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中看到略有不同的示例。

答案 2 :(得分:20)

我就这样做了:

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');重定向到购物车页面,它将保存在历史记录对象中。

享受,迈克尔。

答案 3 :(得分:19)

根据React Router v4 documentation - Redux Deep Integration session

需要深度整合:

  

“能够通过调度操作导航”

但是,他们建议将此方法作为“深度整合”的替代方案:

  

“您可以将提供的历史记录对象传递给您的操作并在其中导航,而不是分派导航操作。”

因此,您可以使用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");
  };
}

答案 4 :(得分:16)

讨厌的问题,花了我很多时间,但最终,我这样解决了:

使用mapDispatchToProps包裹您的容器,并将历史记录传递给export function saveData(history, data) { fetch.post('/save', data) .then((response) => { ... history.push('/url'); }) }; 函数中的操作。在操作中,使用history.push(&#39; / url&#39;)进行导航。

动作:

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

容器:

{{1}}

这适用于 React Router v4.x

答案 5 :(得分:11)

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

pandas

但是要使用此方法,您的现有组件应有权访问this.props.history.push('/new/url'); 对象。我们可以通过

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

    例如:

    history

    <Route path="/profile" component={ViewProfile}/> 可以访问ViewProfile

  2. 如果未直接连接到history

    例如:

    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

答案 6 :(得分:10)

现在,在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

答案 7 :(得分:6)

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

}

答案 8 :(得分:5)

在这种情况下,你将道具传递给你的thunk。所以你只需致电

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

答案 9 :(得分:5)

我提供了一个解决方案,以防其他人有价值。

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

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

接下来,在我定义路由器的Root上,我使用以下内容:

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我导入历史记录并使用pushLater

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

这样,我可以在API调用后推送到新的操作。

希望它有所帮助!

答案 10 :(得分:4)

注意,不要将 react-router@5.2.0react-router-dom@5.2.0history@5.0.0 一起使用。 URL 将在 history.push 或任何其他历史记录指令推送后更新,但导航不适用于 react-router。使用 npm install history@4.10.1 更改历史版本。见React router not working after upgrading to v 5

我认为这个问题是在推送历史时发生的。例如,使用 <NavLink to="/apps"> 在 NavLink.js 中遇到消耗 <RouterContext.Consumer> 的问题。 context.location 在推送到历史记录时更改为具有操作和位置属性的对象。因此 currentLocation.pathname 为空以匹配路径。

答案 11 :(得分:3)

我可以使用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

因此,通过this postData index.jsx this.props.history request.js {},我可以访问this.postData = postData.bind(this)中的constructor() ...然后我可以重复使用此功能不同的组件,只需要确保我记得在grade中包含pattern

答案 12 :(得分: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'))

答案 13 :(得分:2)

这是我的hack(这是我的根级文件,其中混合了一些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()但由于某种原因{{1即使网址已更改,也似乎永远不会更新。但是这样我就有了react-router使用的EXACT实例。我不喜欢把东西放在全球范围内,这是我做的少数事情之一。但它比我见过IMO的任何其他选择都要好。

答案 14 :(得分:2)

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

您必须按照Readme file

中的说明创建商店

最简单的用例:

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

答案 15 :(得分:0)

我在同一个话题上苦苦挣扎。 我正在使用react-router-dom 5,Redux 4和BrowserRouter。 我更喜欢基于函数的组件和挂钩。

您可以这样定义组件

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

const Component = () => {
  ...
  const history = useHistory();
  dispatch(myActionCreator(otherValues, history));
};

您的动作创建者正在关注

const myActionCreator = (otherValues, history) => async (dispatch) => {
  ...
  history.push("/path");
}

如果不需要异步,您当然可以使用更简单的动作创建器

答案 16 :(得分:0)

如果您想在将函数作为值传递给组件的prop时使用历史记录,则可以使用 react-router 4 来简化history prop的结构, <Route/>组件,然后使用history.push()

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

注意:要使其正常工作,您应该将React Router的BrowserRouter组件包装在您的根组件周围(例如,可能在index.js中)

答案 17 :(得分:0)

由于我们在 react router 5 中已经包含了一个历史记录,我们可以通过参考访问它

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

function App() {
   const routerRef = React.useRef();
   const onProductNav = () => {
       const history = routerRef.current.history;
       history.push("product");
   }
return (
    <BrowserRouter ref={routerRef}>
        <Switch>
            <Route path="/product">
                <ProductComponent />
            </Route>
            <Route path="/">
                <HomeComponent />
            </Route>
        </Switch>
    </BrowserRouter>
)
}

答案 18 :(得分: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

接下来,在定义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');
}

答案 19 :(得分:0)

所以我的方法是: -我只使用history.push中的Redirect组件,而不是使用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}/>
  }
}

答案 20 :(得分:0)

反应路由器V4现在允许按以下方式使用历史记录道具:

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

然后可以在任何位置道具可用的位置访问该值 state:{value}不是组件状态。

答案 21 :(得分: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的整个观点。

答案 22 :(得分:0)

/*Step 1*/
myFunction(){  this.props.history.push("/home"); }
/**/
 <button onClick={()=>this.myFunction()} className={'btn btn-primary'}>Go 
 Home</button>

答案 23 :(得分:0)

你可以像这样使用它来登录和manny不同的东西

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>

)