使用react-router V4 以编程方式导航

IT技术 reactjs ecmascript-6 react-router-v4
2021-04-06 23:50:09

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

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

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

6个回答

如果您的目标是浏览器环境,则需要使用react-router-dompackage 而不是react-router. 他们遵循与 React 相同的方法,以便将核心 ( react) 和平台特定代码 ( react-dom, react-native) 分开,但细微的区别是您不需要安装两个单独的包,因此环境包包含所有内容你需要。您可以将其添加到您的项目中:

yarn add react-router-dom

或者

npm i react-router-dom

您需要做的第一件事是提供一个<BrowserRouter>作为应用程序中最顶层的父组件。<BrowserRouter>使用 HTML5 historyAPI 并为您管理它,因此您不必担心自己实例化它并将其<BrowserRouter>作为props传递给组件(就像您在以前的版本中需要做的那样)。

在 V4 中,为了以编程方式导航,您需要访问history可通过 React访问的对象,context只要您将<BrowserRouter> 提供程序组件作为应用程序中最顶层的父组件即可。该库通过上下文公开router对象,对象本身包含history为一个属性。history界面提供了多种导航方法,例如pushreplacegoBack等。您可以在此处查看整个属性和方法列表

Redux/Mobx 用户的重要提示

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

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

connect 和观察者都创建组件,其 shouldComponentUpdate 方法对当前的 props 和下一个 props 进行浅层比较。这些组件只会在至少一个 prop 发生变化时重新渲染。这意味着为了确保它们在位置更改时更新,需要为它们提供一个在位置更改时更改的 prop。

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

  • 连接的组件包装在一个无路径的<Route />. 当前location对象是 a<Route>传递给它渲染的组件的props之一
  • 高阶组件包裹你的连接组件withRouter,它实际上具有相同的效果并location作为props 注入

撇开这一点不谈,有四种以编程方式导航的方法,按推荐排序:

1.- 使用<Route>组件

它提倡声明式风格。在 v4 之前,<Route />组件被放置在组件层次结构的顶部,必须事先考虑路由结构。但是,现在您可以在树中的任何位置拥有<Route>组件,让您可以根据 URL 更好地控制条件渲染。,作为props注入到您的组件中。导航方法(例如, , ...)可用作对象的属性RoutematchlocationhistorypushreplacegoBackhistory

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

如果您想自定义您的组件渲染输出,您需要将您的组件包装在一个函数中并使用该render选项,以便将您想要的任何其他props传递给您的组件,除了match,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.- 使用withRouterHoC

这个高阶组件将注入与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组件

渲染 a<Redirect>将导航到新位置。但请记住,默认情况下,当前位置会替换为新位置,例如服务器端重定向 (HTTP 3xx)。新位置由toprop提供,可以是字符串(要重定向到的 URL)或location对象。如果您想将新条目推送到历史记录中,请同时传递一个pushprops并将其设置为true

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

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

有点气馁,因为context仍然是一个实验性的 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 平台为目标,可通过 package.json 获得react-router-native

如需进一步参考,请随时查看官方文档还有一个由该库的合著者之一制作视频,该视频提供了对 react-router v4 的非常酷的介绍,突出了一些主要变化。

withRouterreact-router-dom. history.push 确实更改了 url,但在<Route>我强制刷新页面之前它似乎不会加载...
2021-05-24 23:50:09
对于那些遇到这个很好的答案的人来说,这withRouter只是一个使用Route引擎盖下的HOC 这意味着它只使用了3 个 props,history、match 和 location在上面的例子中,它似乎push是一个withRouter会添加到的道具ButtonToNavigate,但事实并非如此。props.history.push将不得不改为使用。希望这可以帮助其他有点困惑的人。
2021-05-27 23:50:09
@rauliyohmc 我错了。问题是我有<Router> 一个用 装饰的 React 组件@observer,这会触发这个问题解决方法是@withRouter在每个这样的 React 组件上都有。
2021-06-02 23:50:09
我正在使用 V4,以上工作正常。我花了相当多的时间来处理 V4 路由器,因为似乎有一些奇怪的选择,但以上确实有效。我假设你是withRouter进口react-router-dom
2021-06-11 23:50:09
哇。browserHistory.push('/path/some/where')只是似乎简单多了。作者试图阻止命令式编程,但有时它会更有效!
2021-06-16 23:50:09

完成它的最简单方法:

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

笔记:

  • history prop如果操作不可用,您可能希望将from 父组件传递给要调用该操作的组件。
如何防止组件重新安装history.push并像我们点击一​​样触发更新<Link>
2021-05-21 23:50:09
我使用 4.0 路由器,但道具上没有历史记录键。我该如何解决?
2021-05-29 23:50:09
@Malvineous 很有趣,不知道这个!会试试的!
2021-06-07 23:50:09
我一定遗漏了一些基本的东西,因为这对我来说非常有用,所以我不知道为什么所有的答案都需要如此冗长的解释。
2021-06-10 23:50:09
如果您this.props.history的组件中没有可用的,那么您可以import {withRouter} from 'react-router-dom'然后 export default withRouter(MyComponent)(或const MyComponent = withRouter(...)),它将history在您的组件的道具中插入该项目。
2021-06-20 23:50:09

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

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

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

在 React Router 文档中,查看该<Router> 组件,您会发现您可以通过 prop 传递您自己的历史对象。这是该方案的本质-我们提供的历史对象,以React-Router全球module。

脚步:

  1. 安装历史 npm module -yarn add history 或者 npm install history --save
  2. history.js在您的App.js级别文件夹中创建一个文件(这是我的偏好)

    // src/history.js
    
    import createHistory from 'history/createBrowserHistory';
    export default createHistory();`
    
  3. 像这样将这个历史对象添加到你的路由器组件中

    // src/App.js
    
    import history from '../your/path/to/history.js;'
    <Router history={history}>
    // Route tags here
    </Router>
    
  4. 调整URL就像通过导入之前,你的世界历史的对象:

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

现在一切都应该保持同步,您还可以访问以编程方式设置历史对象的方法,而不是通过组件/容器。

@Spets 在我的例子中,如果我使用这种方法,链接将在推送后正确更新,但组件未正确呈现(例如,更新链接后,除非您强制页面刷新,否则组件不会更新)。你在哪里发现这是推荐的方法?任何链接/来源?
2021-05-24 23:50:09
这是截至 08/17 的推荐方法
2021-05-31 23:50:09
@ScottCoates 我使用上面的示例进行了整理,确实通过提供历史记录作为参数,但是在我自己调试了节点module之后。网络上有一个常见的错误,即使用“BrowserHistory 作为路由器”的导入,而在 react-router-dom 的最新版本中有另一个名为 Router 的对象。将它与上面示例中创建的历史记录结合使用效果很好。
2021-06-06 23:50:09
url 已更新,但页面未根据新根呈现。有什么解决办法吗?为什么触发路径这么难?设计 react 的人脑子进水了?
2021-06-06 23:50:09
此更改对我有用,但这只是因为我在组件之外导航。如果我在像 OP 这样的组件中导航,我会使用 @rauliyohmc 建议的方法,通过使用Route组件传递的道具
2021-06-15 23:50:09

特尔;博士:

if (navigate) {
  return <Redirect to="/" push={true} />
}

简单且声明性的答案是您需要<Redirect to={URL} push={boolean} />结合使用setState()

push: boolean -如果为真,重定向会将新条目推送到历史记录中,而不是替换当前条目。


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

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

附注。该示例使用ES7+ 属性初始化器来初始化状态。如果你有兴趣,也可以看看这里

并非如此,您仍然可以利用 React 的声明性与 <Redirect /> 组件相结合。如果页面没有加载,你可以回退到另一个 <Redirect />
2021-06-04 23:50:09
只有当您知道页面加载时的重定向时,这才有效。如果您正在等待异步调用返回(即使用 Google 或其他方式进行身份验证),那么您将不得不使用其中一种history.push()方法。
2021-06-07 23:50:09
@brittohalloran 使用适当的反应路由器的这种方法的想法是确保您使用 setState 强制重新渲染。
2021-06-10 23:50:09
这应该是可以接受的答案。最简单优雅的解决方案!+1 @lustoykov
2021-06-14 23:50:09
我还在 componentDidUpdate 中将导航设置为 false,因为我的按钮位于标题中,否则只能导航一次。
2021-06-14 23:50:09

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 Router 注入了一些属性,包括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;