react-router v4 使用 history 控制路由跳转
brickspert opened this issue · 93 comments
react-router v4 使用 history 控制路由跳转
问题
当我们使用react-router v3
的时候,我们想跳转路由,我们一般这样处理
- 我们从
react-router
导出browserHistory
。 - 我们使用
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 v4
在 Router
组件中通过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
。看代码~
- 我们自己创建一个
history
// src/history.js
import createHistory from 'history/createBrowserHistory';
export default createHistory();
- 我们使用
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'),
);
- 其他地方我们就可以这样用了
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
组件。嘿嘿。
讲到这里也结束了,我自己目前在使用第三种方法,虽然官方推荐第一种,我觉得用着比较麻烦唉。~
❤️感谢大家
关注公众号「前端技术砖家」,拉你进交流群,大家一起共同交流和进步。
貌似在组件中 可以直接使用 类似
let { history } = this.props; history.push('/cart')
@wagouwan. 不可以的奥。除非你自己传了history过来。
@wsgouwan 你在<Route>
组件的下一级使用是没问题的,但是你在孙子组件,或者redux里面是没法使用的哦。
使用react-router-redux也可以的吧?不过这东西,支持react router4的还在开发中....
store.dispatch(push('/foo'))
@weishijun14 可以的,不过一般开发项目中其实用不到react-router-redux
的,除非有强烈的管理路由的需求~哈哈。
感觉这样子做的话,react展示组件逻辑不纯了,有没有更好的根据store去跳转的方案呢?
@regiondavid 不是很理解你的意思~~
好棒!!!!!!!!!!!!!!
没有用mobx的嘛?
@sysoft 没用过mobx,不过history的使用方式也是一样的。
第三种方法,用在任何地方都可以的哩。
我用第一种了
第一种方法里的"/some/Path"这个路由是在哪里配置的啊, 我也是使用的这种方法,但是 层级比较深,就不能跳转路由,地址栏改变,路由不跳转
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
@dongkeying /some/path
就是普通的路由呀。。 你看看有没有报错信息。贴出来看看
页面没有报错,而且地址栏已经跳转到"/class/cart"的路由上了,但是页面没有跳转,重点就是我再刷新一下,就显示内容了... 然而直接跳转就不能,必须再刷新一下才显示
路由配置信息:
<Provider store={store}> <Router> <Switch> <Route exact path="/" component={Main} /> <Route path="/class/cart" component={Cart}></Route> <Route path="/search" component={Search} /> <Route component={NoMatch}></Route> </Switch> </Router> </Provider>
组件点击的时候:
gotoDetail(item){ // 传参方式是这样吗 ? this.props.history.push({ pathname: '/cart',state:{item}}); this.props.history.push("/class/cart"); } export default withRouter(Tab);
这个跟组件的嵌套深度有关系吗? 可能是我的路由嵌套太深了,是吗
@dongkeying 和嵌套深度没关系。写法也没啥问题呀。。再然后为什么不跳转,这个不太清楚。没报错信息有点奇怪哦。
@shengjudao
这样的,路由组件
<Route exact path="/" component={Home}/>
在Home组件里面可以访问到history
的。
但是子组件就访问不到了哦。除非你手动一级一级传下去。
可是使用了withRouter 不就可以在层级较深的子组件访问到 history 了吗, 是不是就不需要一级一级传下去了
@dongkeying 是的。你的问题很可能代码写错了。既然路由变了,页面没变,肯定路由配置那边有问题。
你好,我想问一下,你在第三种方式hack的时候,import createHistory from 'history/createBrowserHistory'; 不需要npm install --save history 吗?可以直接用?
@yangxiufang1994 不用的,可以直接用。因为react-router v4 帮你安装了history啦。
@brickspert 我只npm install react-router-dom ,我如果不安装,他就会提示我抱错,你也是安装的 react-router-dom 吗?
@yangxiufang1994 是只安装react-router-dom
就可以的呀。
有错误可以贴出来。
@brickspert import createHistory from 'history/createHashHistory'
const history = createHistory()
webpack3打包的时候就报Cannot find module 'history/createHashHistory', 我主要是想再ts 里跳一下路由
@yangxiufang1994 理论上装了react-router-dom就可以直接用了。 你看看你node_modules里面有木有history
@brickspert 嗯嗯,我在重安装一下,试试看,谢谢你。
我是采用您第三种方式,但是history.push之后页面路径会变化,但没有跳转。所以加了forceRefresh:true的初始化参数,强制每次路径变化刷新页面。这样页面就会有刷新,请问 可以有页面不刷新的路由跳转方式吗?
@Lmagic16 你好。我自己开发中也是使用第三种方法的,并没有使用forceRefresh: true呀。
是不是你代码哪里写的问题了。
@brickspert 谢谢你,现在可以了,主要我之前采用的import { BrowserRouter as Router } 加 的方式,现在采用您这种方式,就可以默认跳转了。
@brickspert 谢谢你,现在可以了,主要我之前采用的import { BrowserRouter as Router } 加 <Router></Router>
的方式,现在采用您<Router history={history}></Router>
这种方式,就可以默认跳转了。
@Lmagic16 嗯嗯,不用谢。 全局只能有一个history实例。
import { BrowserRouter as Router }
这个他会自己给你创建一个history实例的,相当于你有俩实例了。就不好使了。
开始试了下官方推荐的方法,发现如果用上redux, 需要 withRouter(connect(...)(MyComponent)) 这样包裹着容器组件才能得到 history
UserAdd.js:70 Uncaught TypeError: Cannot read property 'push' of undefined
一直这个错误,会是什么原因。 "react-router": "^4.2.0"
@Sir0xb 难道不是react-router-dom
吗?
解决了,代码中确实用的"react-router-dom": "^4.2.2"。做了个provider包装了类,在最后导出的时候用了withRouter,所以实体里一直没有history属性。包装之前调用withRouter就好了。
Uncaught Error:
You should not use or withRouter() outside a
请问这个可能是什么原因呢
- 你错误信息没写全啊。。outside a .... 啥?
- 我猜下,
withRouter
要在Router
组件下面的。你全局有没有用Router
呢?是不是路由那边写错了。
@brickspert
不好意思,一个很低级的错误~
非常感谢大佬的博客和耐心回复!!!!!!!!!!
点赞,用了你全家桶教程搭建的框架,十分受用。
@cookcocck 我也遇到这个问题,请问是错在哪里了,能说一下吗?
@brickspert 我使用了你的第三种方法,但是路由跳转的时候,url变了,可是UI确没有变,请问是什么原因呢?一下是我的路由配置,我用了react-loadable实现按需加载。
@SunnyXiao 你的问题解决了么,我没有使用按需加载,但出现了和你一模一样的问题。
@BenlovedLamphere 解决了,直接把路由配置放到app.jsx里面就好
@BenlovedLamphere 看看你路由咋写的
withRouter
const path = {
pathname: '/detail',
state: item,
}
this.props.history.push(path);
传递过去的参数 如果页面刷新了怎么破?
@jayguojianhai 页面刷新state还在的吧?如果不在了,可用query
大神我用了第三种方法,在Router中使用Link的时候报错this.context.history.createHref is not a function,You should not use outside a ,这个时候怎么让Link能访问自己定义的history
@yushiwho 你是用的第二种方法还是第三种?
@brickspert 第三种
@yushiwho 第三种怎么会有 this.context??
@brickspert 找到原因了,我的history用的是4.3.0,更新到最新的4.7.2就行了,4.3.0这个版本的history没有属性createHref,4.4.1加回去了,e20cebd
你好!我的项目可以正常跑起来,但是控制台一直提示:
warning.js:33 Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.
请问这是由于我的按需加载组件所引起的吗?还是别的原因?
谢谢!
@peterpanBest
把package-lock.json文件删掉,把node_modules删掉,
重新 npm install一下
你好,我有个问题
我在项目里用了BrowserRouter,/:id这种类型的路径可以直接访问到this.props.history,但是/:id/:date这样的没有props,加withroute也没用
咋办呢
没事啦解决了谢谢
本人也是用了方法三,也出现了url改变了,UI没更新,请问怎么解决!
@allenwei123 你node_modules删了,重新npm install下试试。前面碰到的都是一些很低级的错误~你再仔细核对下代码
你好 请问用createHistory怎么添加路由跳转提示,官方文档看完没用明白
你好。我使用的是第三种方法。但是必须添加forceRefresh: true才能成功,否则跳转失败。失败时状况如下:第一级路由通过Redirect跳转至第二级路由,二级路由回跳至一级路由失败;但是在二级路由所在页面刷新一次,则能跳转回一级路由;二级路由中操作即为 this.props.history.push('/') 。不知道为什么??
@wangchuan113057 我觉得是你姿势不对。第三种方法一共三步。你再核对下。尤其是第二步的:
<Router history={history}> //这里的history对吗
按照你的步伐走的.... 就是这个写法。 在二级路由也是可以成功获取到history的,但是我发现从一级路由进入二级路由,在二级路由操作 this.props.history.push('/'),history 的action属性变成了replace,而不是push....我进行了监听发现了action 由push / 转变成了 replace /home
@brickspert 路由中有#的话 每次都会重新请求后台,怎么才能禁用# ?
@wangchuan113057 大兄弟,我第三种方法不是写的。文件里面跳转路由,这样用吗?
import history from './history';
history.push('xx');
你试试?
或者说,是不是路由里面配置拦截了。碰到/
,自动定位到/home
????
@PhotonAlpha 不是很懂你的意思呀~ 是说hashHistory
吗?
按理单页面应用,不管有没有#
,都不会向后台去请求呀。姿势不对?
@brickspert 非常感谢大佬能抽空回复我。
我重新描述一下,我使用的是 3. hack
的配置,我在页面中打算使用锚点功能, <a href="#API" >Title</a>
,点击之后 页面URL变成http://localhost:4200/reveal#API
,
这个时候,这个组件会重新加载
constructor(props) {
//这里会被调用
super(props);
}
这个情况是正常的吗?
大神我找到原因了:
我是参考你的 全家桶 文章的基础上使用了history,这个问题我刚才在全家桶文章的教程中验证过了。
bundle改造 src/router/Bundle.js
在 src/router/router.js 文件中
import UserInfo from 'bundle-loader?lazy&name=userInfo!pages/UserInfo/UserInfo';
const createComponent = (component) => (props) => (
<Bundle load={component}>
{
(Component) => Component ? <Component {...props} /> : <Loading/>
}
</Bundle>
);
const getRouter = () => (
<Router>
<div>
<ul>
<li><Link to="/">首页</Link></li>
<li><Link to="/userinfo">UserInfo</Link></li>
</ul>
<Switch>
<Route exact path="/" component={createComponent(Home)}/>
<Route path="/userinfo" component={createComponent(UserInfo)}/>
</Switch>
</div>
</Router>
);
改造了component变成按需加载,这个时候 如果我URL中加入了变成了http://localhost:4200/reveal#API
之后, 组建会重新load(
constructor(props) { super(props); } componentDidMount() {} render() {}
) 都会被调用
当我把代码换回原始版本
import UserInfo from 'pages/UserInfo/UserInfo';
<Route path="/counter" component={UserInfo}/>
URL里面出现 #hash的时候 只会render了。
大神可以帮我把Bundle 组件里面bug修复一下吗?
谢谢了..经过你的提醒我发现是路由拦截的问题....
对于方法三,如果用的是HashRouter,那么在history.js中应引入
import createHistory from 'history/createHashHistory';
同时,HashRouter标签中不再声明history属性
在需要使用的地方,直接引入history.js即可 import history from './history';
这样便不会出现url变化,但路由不跳转的情况,也就不需要forceRefresh: true
@hugeorange 这个props
里面的history对象,就是我们history.js
里面定义的那个hisory对象。
并没有说替换路由,是自定义路由。
第二步,我们不是把自己定义的history传进去了么。
connected-react-router
请问有没有小demo可以给参考一下,因为对redux还不是很了解
使用第三种方法,完美解决
@a12366456 将全局的方法绑定到react的配置文件中,使用脚手架创建的项目可以在你的index.js里面的顶部进行绑定。
// 在你使用React之前
...
import { createBrowserHistory /*createHashHistory */ } from 'history'
...
...
Object.assign(React.Component.prototype, {
...
$router: createBrowserHistory()
})
...
// 使用React.Component
...
componentWillMount () {
console.log(this.$router)
}
...
如果使用的hash,这种该怎么解决呢?
已经解决了,把import createHistory from 'history/createBrowserHistory'换为import createHistory from 'history/createHashHistory'就可以
开始试了下官方推荐的方法,发现如果用上redux, 需要 withRouter(connect(...)(MyComponent)) 这样包裹着容器组件才能得到 history
这里才是重点啊,因为有时候需要在组件外的地方使用history,withRouter只能对组件使用,所以第三种方式更灵活
我用第三种方法实现了 但是 history.push("/login") 只改变了url 并没有刷新
我用第三种方法实现了 但是 history.push("/login") 只改变了url 并没有刷新
你确定你做了这一步<Router history={history}>
我用第三种方法实现了 但是 history.push("/login") 只改变了url 并没有刷新
你确定你做了这一步
<Router history={history}>
多谢,之前的是用browserRouter包裹的,我换成了Router就可以了
我用第三种方法实现了 但是 history.push("/login") 只改变了url 并没有刷新
mark
mark
完完全全按照第三步来的,跳转后还是不加载页面,是不是5.0版本的history又有改动啊。
我用第三种方法实现了 但是 history.push("/login") 只改变了url 并没有刷新
老哥最后解决了么?
我用第三种方法实现了 但是 history.push("/login") 只改变了url 并没有刷新
你确定你做了这一步
<Router history={history}>
多谢,之前的是用browserRouter包裹的,我换成了Router就可以了
Nice!
我用第三种方法实现了 但是 history.push("/login") 只改变了url 并没有刷新
老哥最后解决了么?
我把hisotry5降级为4后解决了