brickspert / blog

个人技术博客,博文写在 Issues 里。
4.07k stars 548 forks source link

react-router v4 使用 history 控制路由跳转 #3

Open brickspert opened 6 years ago

brickspert commented 6 years ago

react-router v4 使用 history 控制路由跳转

问题

当我们使用react-router v3的时候,我们想跳转路由,我们一般这样处理

  1. 我们从react-router导出browserHistory
  2. 我们使用browserHistory.push()等等方法操作路由跳转。

类似下面这样

import browserHistory from 'react-router';

export function addProduct(props) {
  return dispatch =>
    axios.post(`xxx`, props, config)
      .then(response => {
        browserHistory.push('/cart'); //这里
      });
}

but!! 问题来了,在react-router v4中,不提供browserHistory等的导出~~

那怎么办?我如何控制路由跳转呢???

解决方法

1. 使用 withRouter

withRouter高阶组件,提供了history让你使用~

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

这是官方推荐做法哦。但是这种方法用起来有点难受,比如我们想在redux里面使用路由的时候,我们只能在组件把history传递过去。。

就像问题章节的代码那种场景使用,我们就必须从组件中传一个history参数过去。。。

2. 使用 Context

react-router v4Router 组件中通过Contex暴露了一个router对象~

在子组件中使用Context,我们可以获得router对象,如下面例子~

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

当然,这种方法慎用~尽量不用。因为react不推荐使用contex哦。在未来版本中有可能被抛弃哦。

3. hack

其实分析问题所在,就是v3中把我们传递给Router组件的history又暴露出来,让我们调用了~~

react-router v4 的组件BrowserRouter自己创建了history, 并且不暴露出来,不让我们引用了。尴尬~

我们可以不使用推荐的BrowserRouter,依旧使用Router组件。我们自己创建history,其他地方调用自己创建的history。看代码~

  1. 我们自己创建一个history
// src/history.js

import createHistory from 'history/createBrowserHistory';

export default createHistory();
  1. 我们使用Router组件
// src/index.js

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

ReactDOM.render(
  <Provider store={store}>
    <Router history={history}>
      ...
    </Router>
  </Provider>,
  document.getElementById('root'),
);
  1. 其他地方我们就可以这样用了
import history from './history';

export function addProduct(props) {
  return dispatch =>
    axios.post(`xxx`, props, config)
      .then(response => {
        history.push('/cart'); //这里
      });
}

4. 我非要用BrowserRouter

确实,react-router v4推荐使用BrowserRouter组件,而在第三个解决方案中,我们抛弃了这个组件,又回退使用了Router组件。

怎么办。 你去看看BrowserRouter源码,我觉得你就豁然开朗了。

源码非常简单,没什么东西。我们完全自己写一个BrowserRouter组件,然后替换第三种解决方法中的Router组件。嘿嘿。

讲到这里也结束了,我自己目前在使用第三种方法,虽然官方推荐第一种,我觉得用着比较麻烦唉。~

❤️感谢大家

关注公众号「前端技术砖家」,拉你进交流群,大家一起共同交流和进步。

image

wsgouwan commented 6 years ago

貌似在组件中 可以直接使用 类似 let { history } = this.props; history.push('/cart')

brickspert commented 6 years ago

@wagouwan. 不可以的奥。除非你自己传了history过来。

brickspert commented 6 years ago

@wsgouwan 你在<Route>组件的下一级使用是没问题的,但是你在孙子组件,或者redux里面是没法使用的哦。

terminalqo commented 6 years ago

使用react-router-redux也可以的吧?不过这东西,支持react router4的还在开发中....

terminalqo commented 6 years ago

store.dispatch(push('/foo'))

brickspert commented 6 years ago

@weishijun14 可以的,不过一般开发项目中其实用不到react-router-redux的,除非有强烈的管理路由的需求~哈哈。

regiondavid commented 6 years ago

感觉这样子做的话,react展示组件逻辑不纯了,有没有更好的根据store去跳转的方案呢?

brickspert commented 6 years ago

@regiondavid 不是很理解你的意思~~

hazeFlame commented 6 years ago

好棒!!!!!!!!!!!!!!

sysoft commented 6 years ago

没有用mobx的嘛?

brickspert commented 6 years ago

@sysoft 没用过mobx,不过history的使用方式也是一样的。 第三种方法,用在任何地方都可以的哩。

hazeFlame commented 6 years ago

我用第一种了

dongkeying commented 6 years ago

第一种方法里的"/some/Path"这个路由是在哪里配置的啊, 我也是使用的这种方法,但是 层级比较深,就不能跳转路由,地址栏改变,路由不跳转

shengjudao commented 6 years ago

import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import registerServiceWorker from './registerServiceWorker'; import RouterComponent from './router/router'; import {BrowserRouter} from 'react-router-dom'; import {Provider} from 'react-redux'; import stores from './store/store'; const storeA = stores();

ReactDOM.render(

, document.getElementById('root'));

registerServiceWorker();

我直接app组件里面,用this.props.history.push()还是能跳转,为什么啊,没有用withRouter

brickspert commented 6 years ago

@dongkeying /some/path就是普通的路由呀。。 你看看有没有报错信息。贴出来看看

dongkeying commented 6 years ago

页面没有报错,而且地址栏已经跳转到"/class/cart"的路由上了,但是页面没有跳转,重点就是我再刷新一下,就显示内容了... 然而直接跳转就不能,必须再刷新一下才显示 路由配置信息: `

        </Provider>`

组件点击的时候: gotoDetail(item){ // 传参方式是这样吗 ? this.props.history.push({ pathname: '/cart',state:{item}}); this.props.history.push("/class/cart"); } export default withRouter(Tab); 这个跟组件的嵌套深度有关系吗? 可能是我的路由嵌套太深了,是吗

brickspert commented 6 years ago

@dongkeying 和嵌套深度没关系。写法也没啥问题呀。。再然后为什么不跳转,这个不太清楚。没报错信息有点奇怪哦。

brickspert commented 6 years ago

@shengjudao 这样的,路由组件 <Route exact path="/" component={Home}/> 在Home组件里面可以访问到history的。 但是子组件就访问不到了哦。除非你手动一级一级传下去。

dongkeying commented 6 years ago

可是使用了withRouter 不就可以在层级较深的子组件访问到 history 了吗, 是不是就不需要一级一级传下去了

brickspert commented 6 years ago

@dongkeying 是的。你的问题很可能代码写错了。既然路由变了,页面没变,肯定路由配置那边有问题。

yangxiufang1994 commented 6 years ago

你好,我想问一下,你在第三种方式hack的时候,import createHistory from 'history/createBrowserHistory'; 不需要npm install --save history 吗?可以直接用?

brickspert commented 6 years ago

@yangxiufang1994 不用的,可以直接用。因为react-router v4 帮你安装了history啦。

yangxiufang1994 commented 6 years ago

@brickspert 我只npm install react-router-dom ,我如果不安装,他就会提示我抱错,你也是安装的 react-router-dom 吗?

brickspert commented 6 years ago

@yangxiufang1994 是只安装react-router-dom就可以的呀。

有错误可以贴出来。

yangxiufang1994 commented 6 years ago

@brickspert import createHistory from 'history/createHashHistory' const history = createHistory() webpack3打包的时候就报Cannot find module 'history/createHashHistory', 我主要是想再ts 里跳一下路由 image

brickspert commented 6 years ago

@yangxiufang1994 理论上装了react-router-dom就可以直接用了。 你看看你node_modules里面有木有history

yangxiufang1994 commented 6 years ago

@brickspert 嗯嗯,我在重安装一下,试试看,谢谢你。

Lmagic16 commented 6 years ago

我是采用您第三种方式,但是history.push之后页面路径会变化,但没有跳转。所以加了forceRefresh:true的初始化参数,强制每次路径变化刷新页面。这样页面就会有刷新,请问 可以有页面不刷新的路由跳转方式吗?

brickspert commented 6 years ago

@Lmagic16 你好。我自己开发中也是使用第三种方法的,并没有使用forceRefresh: true呀。 是不是你代码哪里写的问题了。

Lmagic16 commented 6 years ago

@brickspert 谢谢你,现在可以了,主要我之前采用的import { BrowserRouter as Router } 加 的方式,现在采用您这种方式,就可以默认跳转了。

Lmagic16 commented 6 years ago

@brickspert 谢谢你,现在可以了,主要我之前采用的import { BrowserRouter as Router } 加 <Router></Router> 的方式,现在采用您<Router history={history}></Router>这种方式,就可以默认跳转了。

brickspert commented 6 years ago

@Lmagic16 嗯嗯,不用谢。 全局只能有一个history实例。

import { BrowserRouter as Router }这个他会自己给你创建一个history实例的,相当于你有俩实例了。就不好使了。

zhangwanli09 commented 6 years ago

开始试了下官方推荐的方法,发现如果用上redux, 需要 withRouter(connect(...)(MyComponent)) 这样包裹着容器组件才能得到 history

Sir0xb commented 6 years ago

UserAdd.js:70 Uncaught TypeError: Cannot read property 'push' of undefined
一直这个错误,会是什么原因。 "react-router": "^4.2.0"

brickspert commented 6 years ago

@Sir0xb 难道不是react-router-dom吗?

Sir0xb commented 6 years ago

解决了,代码中确实用的"react-router-dom": "^4.2.2"。做了个provider包装了类,在最后导出的时候用了withRouter,所以实体里一直没有history属性。包装之前调用withRouter就好了。

cookcocck commented 6 years ago

Uncaught Error: You should not use or withRouter() outside a

请问这个可能是什么原因呢
brickspert commented 6 years ago

@cookcocck

  1. 你错误信息没写全啊。。outside a .... 啥?
  2. 我猜下,withRouter要在Router组件下面的。你全局有没有用Router呢?是不是路由那边写错了。
cookcocck commented 6 years ago

@brickspert
不好意思,一个很低级的错误~ 非常感谢大佬的博客和耐心回复!!!!!!!!!!

JashonWang commented 6 years ago

点赞,用了你全家桶教程搭建的框架,十分受用。

steve-yuan-8276 commented 6 years ago

@cookcocck 我也遇到这个问题,请问是错在哪里了,能说一下吗?

SunnyXiao commented 6 years ago

@brickspert 我使用了你的第三种方法,但是路由跳转的时候,url变了,可是UI确没有变,请问是什么原因呢?一下是我的路由配置,我用了react-loadable实现按需加载。 image
image

BenlovedLamphere commented 6 years ago

@SunnyXiao 你的问题解决了么,我没有使用按需加载,但出现了和你一模一样的问题。

SunnyXiao commented 6 years ago

@BenlovedLamphere 解决了,直接把路由配置放到app.jsx里面就好

BenlovedLamphere commented 6 years ago
2018-03-26 14 49 27

哪位大神能帮我看看用的方法三,结合《从零开始……》里的计数器例子做的JS跳转,究竟哪里出了问题,满足条件后,URL会成功改变,但UI并未重新加载,还是原来页面的UI。

brickspert commented 6 years ago

@BenlovedLamphere 看看你路由咋写的

jayguojianhai commented 6 years ago

withRouter const path = { pathname: '/detail', state: item, } this.props.history.push(path); 传递过去的参数 如果页面刷新了怎么破?

brickspert commented 6 years ago

@jayguojianhai 页面刷新state还在的吧?如果不在了,可用query

BenlovedLamphere commented 6 years ago

@brickspert

2018-03-28 19 33 08

路由这么写的。

另已按要求将代码包上传至您指定邮箱,

十分感谢您的指导。

向你致敬。

yushiwho commented 6 years ago

大神我用了第三种方法,在Router中使用Link的时候报错this.context.history.createHref is not a function,You should not use outside a ,这个时候怎么让Link能访问自己定义的history