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

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

来实现
import { browserHistory } from 'react-router'
browserHistory.push('/path/some/where')
但是我在v4中找不到这样的接口 如何使用v4导航?< / p >
349805 次浏览

如果你的目标是浏览器环境,你需要使用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中,为了以编程方式导航,你需要访问history对象,它可以通过React context获得,只要你在应用程序中有一个<BrowserRouter> 提供者组件作为最顶层的父组件。标准库通过上下文公开router对象,该对象本身包含history作为属性。history接口提供了一些导航方法,比如pushreplacegoBack等等。你可以检查属性和方法的整个列表context0。

< >强

Redux/Mobx用户注意事项

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

这是因为react-routerlocation传递给使用上下文模型的组件。

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

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

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

撇开这个不谈,有四种按推荐顺序进行编程导航的方法:

<Route />1它提倡声明式风格。在v4版本之前,<Route />组件被放置在组件层次结构的顶部,必须事先考虑路由结构。然而,现在你可以在你的树中有<Route>组件<Route />2,允许你根据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>
);

< h3 > 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组件

渲染一个<Redirect>将导航到一个新的位置。但请记住,默认情况下,当前位置被新位置取代,就像服务器端重定向(HTTP 3xx)。新位置由to prop提供,它可以是字符串(要重定向到的URL)或location对象。如果你想要将一个新条目推入历史记录,也传递一个push道具,并将其设置为true

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

4.-通过上下文手动访问router

有点沮丧,因为上下文仍然是一个实验性的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,
}),
};

不用说,还有其他路由器组件是针对非浏览器生态系统的,比如<NativeRouter>,它复制了导航堆栈在内存中,目标是React Native平台,可通过react-router-native包获得。

对于任何进一步的参考,不要犹豫,看看官方文档。还有一个视频是由标准库的一个共同作者编写的,它提供了一个非常酷的react-router v4的介绍,突出了一些主要的变化。

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

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

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

在React路由器文档中,看一下<Router> 组件,你可以看到你有能力通过道具传递你自己的历史对象。这是解决方案的本质——从全球< em > < / em >模块我们提供历史对象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. Add this history object to your Router component like so

    // src/App.js
    
    
    import history from '../your/path/to/history.js;'
    <Router history={history}>
    // Route tags here
    </Router>
    
  4. Adjust the URL just like before by importing your global history object:

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

Everything should stay synced up now, and you also have access to a way of setting the history object programmatically and not via a component/container.

我已经测试v4好几天了。到目前为止,我很喜欢它!过一段时间就明白了。

我也有同样的问题,我发现用下面的方法处理它是最好的(甚至可能是它的目的)。它使用state、三元操作符和<Redirect>

在构造函数()中

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

希望能有所帮助。让我知道。

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

完整示例在这里。 阅读更多在这里.

.

PS.这个例子使用ES7+ Property Initializers来初始化状态。看here,如果你感兴趣的话。

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

最简单的方法是:

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

注意:

  • 你可能想要将history prop从父组件传递给你想要调用该动作的组件(如果它不可用)。

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

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

我的答案类似于亚历克斯的。我不知道为什么React-Router把这个做得如此复杂。为什么我必须用一个HoC来包装我的组件,只是为了访问本质上是全局的?

不管怎样,如果你看一下他们是如何实现<BrowserRouter>的,它只是历史的一个小包装。

我们可以把这段历史提取出来,这样我们就可以从任何地方导入。然而,诀窍是,如果你在做服务器端渲染,并且你试图import历史模块,它不会工作,因为它使用的是浏览器api。但这没关系,因为我们通常只在响应单击或其他客户端事件时重定向。因此,假装一下是可以的:

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

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

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

我为此纠结了一段时间——如此简单,却又如此复杂,因为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>

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

第一步:只需要在上面导入一个东西:

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

第二步:在Route中,传递历史记录:

<Route
exact
path='/posts/add'
render={({history}) => (
<PostAdd history={history} />
)}
/>

第三步:历史被接受为下一个组件的道具的一部分,所以你可以简单地:

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

这很简单,也很有力。

如此:

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


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


export default SomeComponent;

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

目前还不清楚为什么以前没有人提供这种解决方案。我希望它能有所帮助,如果你看到任何问题,请告诉我。

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

如果使用函数组件,请使用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路由器向页面组件注入一些属性,包括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;
this.props.history.push("/url")
如果你在你的组件中没有找到this.props.history, 然后试试这个

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

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

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