以编程方式导航使用反应路由器V4
我刚刚从v3replacereact-router
到v4。
但我不知道如何以编程方式在Component
的成员函数中导航。 即在handleClick()
函数我想导航到/path/some/where
处理一些数据。 我曾经这样做:
import { browserHistory } from 'react-router' browserHistory.push('/path/some/where')
但是我在v4中找不到这样的界面。
我如何使用v4导航?
如果您的目标是浏览器环境,则需要使用react-router-dom
软件包,而不是react-router
。 他们采取与React相同的方法,以分离核心,( react
)和特定于平台的代码( react-dom
, react-native
),其细微区别在于你不需要安装两个独立的包,所以环境包包含你需要的一切。 您可以将其添加到您的项目中:
yarn add react-router-dom
要么
npm i -S react-router-dom
您需要做的第一件事是提供一个<BrowserRouter>
作为您的应用程序中最顶级的父组件。 <BrowserRouter>
使用HTML5 history
API并为您进行pipe理,因此您不必担心自己将其实例化并将其作为道具传递给<BrowserRouter>
组件(正如您在以前版本中所做的那样)。
在V4中,为了以编程方式进行导航,只要您有一个<BrowserRouter>
提供程序组件作为您的应用程序中最顶级的父项,就需要访问通过React context
可用的history
对象。 该库通过上下文暴露router
对象,该对象本身包含作为属性的history
。 history
界面提供了几种导航方法,例如push
, replace
和goBack
等等。 您可以在这里查看整个属性和方法列表。
Redux / Mobx用户的重要说明
如果在应用程序中使用redux或mobx作为状态pipe理库,则可能遇到组件位置感知问题,这些问题在触发URL更新后不会重新呈现
这是因为react-router
使用上下文模型将location
传递给组件。
连接和观察者都创build组件,它们的shouldComponentUpdate方法对他们当前的道具和他们的下一个道具进行浅层的比较。 这些组件只有在至less有一个道具改变时才会重新渲染。 这意味着为了确保他们在位置变化时进行更新,当位置变化时,他们需要被赋予一个改变的道具。
解决这个问题的两种方法是:
- 将你的连接组件包装在一个无path的
<Route />
。 当前location
对象是<Route>
传递给它所呈现的组件的道具之一 - 使用
withRouter
高阶组件包装连接的组件,事实上它具有相同的效果,并将其作为道具注入location
除此之外,有四种方法可以通过编程方式进行导航,按照build议进行sorting:
1.-使用<Route>
组件
它促进了声明式的风格。 在v4之前, <Route />
组件被放置在组件层次结构的顶部,事先必须考虑到你的路由结构。 但是,现在您可以在树中的任何位置使用<Route>
组件,从而可以根据URL有条件地进行更精细的控制。 Route
注入match
, location
和history
作为道具到您的组件。 导航方法(例如push
, replace
, goBack
…)可作为history
对象的属性。
有三种方法可以使用component
, render
或children
道具来渲染path,但是不要在同一个path中使用多个path。 select取决于用例,但前两个选项基本上只会呈现您的组件,如果path
匹配url的位置,而对于children
组件,将呈现无论path匹配的位置与否(有用的基础上调整UI在URL匹配上)。
如果你想自定义你的组件渲染输出 ,你需要将你的组件包装在一个函数中,并使用render
选项,以传递到你的组件的任何其他道具,除了match
, location
和history
。 一个例子来说明:
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, }).isRequired, }; export default withRouter(ButtonToNavigate);
3.-使用Redirect
组件
渲染一个<Redirect>
将导航到一个新的位置。 但请记住, 默认情况下 ,当前位置被replace为新的位置,如服务器端redirect(HTTP 3xx)。 新位置由prop提供,可以是string(要redirect到的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-router-native
包提供React Native平台。
对于任何进一步的参考,不要犹豫,看看官方文件 。 还有一个由图书馆的合作者之一制作的video ,提供了一个非常酷的介绍,反应路由器v4,突出一些重大变化。
最简单的方法来完成它:
this.props.history.push("/new/url")
注意:
- 如果不可用,您可能希望将
history
prop
从父组件传递到要调用该行为的组件。
在迁移到React-Router v4时,我遇到了类似的问题,所以我会尝试在下面解释我的解决scheme。
请不要认为这个答案是解决问题的正确方法,我想有一个很好的机会,因为React Router v4变得更加成熟,并且会出现beta(它甚至可能已经存在,我只是没有发现它) 。
对于上下文,我有这个问题,因为我偶尔使用Redux-Saga
以编程方式更改历史logging对象(说当用户成功validation)。
在React Router文档中,看看<Router>
组件 ,你可以看到你有能力通过prop来传递你自己的历史对象。 这是解决scheme的本质 – 我们将历史对象从全局模块提供给React-Router
。
脚步:
- 安装历史npm模块 –
yarn add history
或npm install history --save
-
在
App.js
级别的文件夹中创build一个名为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>
-
通过导入您的全局历史logging对象,像以前一样调整url:
import history from '../your/path/to/history.js;' history.push('new/path/here/');
一切都应该保持现在同步,并且你也有机会通过程序设置历史对象,而不是通过组件/容器。
TL; DR:
if (navigate) { return <Redirect to="/" push={true} /> }
简单的声明性答案是您需要使用<Redirect to={URL} push={boolean} />
与setState()
push:boolean –如果为true,redirect会将新条目推送到历史logging中,而不是replace当前的条目。
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 +属性初始化程序来初始化状态。 如果你有兴趣,也可以在这里看看。
我的答案与Alex的相似。 我不确定为什么React-Router使这个如此复杂。 为什么我必须用HoC封装我的组件才能访问本质上是全局的?
无论如何,如果你看看他们如何实现<BrowserRouter>
,它只是一个关于历史的小包装。
我们可以把这个历史放一下,以便我们可以从任何地方导入它。 但是,诀窍是,如果您正在进行服务器端渲染,并且您尝试import
历史logging模块,则它将无法工作,因为它使用纯浏览器API。 但这没关系,因为我们通常只会响应点击或其他客户端事件而redirect。 因此假冒它可能是可以的:
// history.js if(__SERVER__) { module.exports = {}; } else { module.exports = require('history').createBrowserHistory(); }
在webpack的帮助下,我们可以定义一些variables,所以我们知道我们在什么环境:
plugins: [ new DefinePlugin({ '__SERVER__': 'false', '__BROWSER__': 'true', // you really only need one of these, but I like to have both }),
现在你可以
import history from './history';
从任何地方。 它只会返回服务器上的空模块。
如果你不想使用这些魔法variables,你只require
在全局对象中需要它(在你的事件处理程序中)。 import
将不起作用,因为它只能在顶层工作。
我已经testing了几天的v4,现在我爱它了! 这一段时间才有意义。
我也有同样的问题,我发现像下面的工作最好(甚至可能是如何打算)处理它。 它使用状态,一个三元运算符和<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> ) }
在点击处理程序()
this.setState({ redirectTo: '/path/some/where' });
希望它有帮助。 让我知道。
也可以简单地使用道具:this.props.history.push('new_url')
有时候我更喜欢通过应用程序切换路线,然后通过button,这是一个最小的工作示例对我有用:
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> ) } }
第一步:只有一件事情可以在上面导入:
import {Route} from 'react-router-dom';
第2步:在你的路线,通过历史:
<Route exact path='/posts/add' render={({history}) => ( <PostAdd history={history} /> .)}/>
第3步:历史被接受为下一个组件的道具的一部分,所以你可以简单地:
this.props.history.push('/');
这很容易,而且非常强大。
我在这方面挣扎了一段时间 – 这么简单,但又太复杂了,因为ReactJS只是一种完全不同的Web应用程序编写方式,对我们老年人来说是非常陌生的!
我创build了一个独立的组件来抽象掉这个混乱:
// 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>
由于没有其他方法来处理这个可怕的devise,我写了一个使用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>