brickspert / blog

个人技术博客,博文写在 Issues 里。
4.09k stars 547 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

brickspert commented 6 years ago

@yushiwho 你是用的第二种方法还是第三种?

yushiwho commented 6 years ago

@brickspert 第三种

brickspert commented 6 years ago

@yushiwho 第三种怎么会有 this.context??

yushiwho commented 6 years ago

@brickspert 找到原因了,我的history用的是4.3.0,更新到最新的4.7.2就行了,4.3.0这个版本的history没有属性createHref,4.4.1加回去了,e20cebd

peterpanBest commented 6 years ago

你好!我的项目可以正常跑起来,但是控制台一直提示: 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. 请问这是由于我的按需加载组件所引起的吗?还是别的原因? 谢谢!

brickspert commented 6 years ago

@peterpanBest 把package-lock.json文件删掉,把node_modules删掉, 重新 npm install一下

hello-astar commented 6 years ago

你好,我有个问题 我在项目里用了BrowserRouter,/:id这种类型的路径可以直接访问到this.props.history,但是/:id/:date这样的没有props,加withroute也没用 咋办呢

hello-astar commented 6 years ago

没事啦解决了谢谢

allenwei123 commented 6 years ago

本人也是用了方法三,也出现了url改变了,UI没更新,请问怎么解决!

brickspert commented 6 years ago

@allenwei123 你node_modules删了,重新npm install下试试。前面碰到的都是一些很低级的错误~你再仔细核对下代码

allenwei123 commented 6 years ago

谢了,我改成绑定在props就可以啦!应该是没有触发内部属性的get

胡程威 邮箱:allenwei123@163.com

签名由 网易邮箱大师 定制

在2018年05月07日 22:30,砖家 写道:

@allenwei123 你node_modules删了,重新npm install下试试。前面碰到的都是一些很低级的错误~你再仔细核对下代码

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

Buhaiyang commented 6 years ago

你好 请问用createHistory怎么添加路由跳转提示,官方文档看完没用明白

brickspert commented 6 years ago

@Buhaiyang https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/Prompt.md

douge1994 commented 6 years ago

你好。我使用的是第三种方法。但是必须添加forceRefresh: true才能成功,否则跳转失败。失败时状况如下:第一级路由通过Redirect跳转至第二级路由,二级路由回跳至一级路由失败;但是在二级路由所在页面刷新一次,则能跳转回一级路由;二级路由中操作即为 this.props.history.push('/') 。不知道为什么??

brickspert commented 6 years ago

@wangchuan113057 我觉得是你姿势不对。第三种方法一共三步。你再核对下。尤其是第二步的:

<Router history={history}>  //这里的history对吗
douge1994 commented 6 years ago

按照你的步伐走的.... 就是这个写法。 在二级路由也是可以成功获取到history的,但是我发现从一级路由进入二级路由,在二级路由操作 this.props.history.push('/'),history 的action属性变成了replace,而不是push....我进行了监听发现了action 由push / 转变成了 replace /home

PhotonAlpha commented 6 years ago

@brickspert 路由中有#的话 每次都会重新请求后台,怎么才能禁用# ?

brickspert commented 6 years ago

@wangchuan113057 大兄弟,我第三种方法不是写的。文件里面跳转路由,这样用吗?

import history from './history';

history.push('xx');

你试试?

或者说,是不是路由里面配置拦截了。碰到/,自动定位到/home????

brickspert commented 6 years ago

@PhotonAlpha 不是很懂你的意思呀~ 是说hashHistory吗?

按理单页面应用,不管有没有#,都不会向后台去请求呀。姿势不对?

PhotonAlpha commented 6 years ago

@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修复一下吗?

douge1994 commented 6 years ago

谢谢了..经过你的提醒我发现是路由拦截的问题....

GuoTeng commented 6 years ago

对于方法三,如果用的是HashRouter,那么在history.js中应引入 import createHistory from 'history/createHashHistory'; 同时,HashRouter标签中不再声明history属性 在需要使用的地方,直接引入history.js即可 import history from './history'; 这样便不会出现url变化,但路由不跳转的情况,也就不需要forceRefresh: true

hugeorange commented 6 years ago

按照您的 第三种方法成功了,但是有个疑问 现在在路由组件的儿子组件上:用 props依旧能看到 路由对象:history、location、match,不是说 我们用createBrowserHistoryreact-router-domBrowserRouter 代替了吗? 为什么还能看到这三个对象 image

brickspert commented 6 years ago

@hugeorange 这个props里面的history对象,就是我们history.js里面定义的那个hisory对象。 并没有说替换路由,是自定义路由。 第二步,我们不是把自己定义的history传进去了么。

hxmilyy commented 6 years ago

connected-react-router

linbaishuang commented 6 years ago

请问有没有小demo可以给参考一下,因为对redux还不是很了解

DuRunzhe commented 5 years ago

使用第三种方法,完美解决

0x1af2aec8f957 commented 5 years ago

@a12366456 将全局的方法绑定到react的配置文件中,使用脚手架创建的项目可以在你的index.js里面的顶部进行绑定。

// 在你使用React之前

...
import { createBrowserHistory /*createHashHistory */ } from 'history'
...

...
Object.assign(React.Component.prototype, {
  ...
  $router: createBrowserHistory()
})
...

// 使用React.Component

...
componentWillMount () { 
  console.log(this.$router)
}
...
ZSH-HSZ commented 5 years ago

如果使用的hash,这种该怎么解决呢?

ZSH-HSZ commented 5 years ago

已经解决了,把import createHistory from 'history/createBrowserHistory'换为import createHistory from 'history/createHashHistory'就可以

refanbanzhang commented 5 years ago

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

这里才是重点啊,因为有时候需要在组件外的地方使用history,withRouter只能对组件使用,所以第三种方式更灵活

catsayer-heat commented 5 years ago

我用第三种方法实现了 但是 history.push("/login") 只改变了url 并没有刷新

refanbanzhang commented 5 years ago

我用第三种方法实现了 但是 history.push("/login") 只改变了url 并没有刷新

你确定你做了这一步<Router history={history}>

FrookeBZHW commented 5 years ago

我用第三种方法实现了 但是 history.push("/login") 只改变了url 并没有刷新

你确定你做了这一步<Router history={history}>

多谢,之前的是用browserRouter包裹的,我换成了Router就可以了

794086163 commented 5 years ago

我用第三种方法实现了 但是 history.push("/login") 只改变了url 并没有刷新

ZhangMingZhao1 commented 5 years ago

mark

gladiss commented 4 years ago

22 这是什么

hazeFlame commented 4 years ago

22 这是什么

redux吧

erdong0604 commented 4 years ago

mark

Sondergm commented 3 years ago

完完全全按照第三步来的,跳转后还是不加载页面,是不是5.0版本的history又有改动啊。

Sondergm commented 3 years ago

我用第三种方法实现了 但是 history.push("/login") 只改变了url 并没有刷新

老哥最后解决了么?

greatbear412 commented 3 years ago

我用第三种方法实现了 但是 history.push("/login") 只改变了url 并没有刷新

你确定你做了这一步<Router history={history}>

多谢,之前的是用browserRouter包裹的,我换成了Router就可以了

Nice!

lizijie123 commented 3 years ago

我用第三种方法实现了 但是 history.push("/login") 只改变了url 并没有刷新

老哥最后解决了么?

我把hisotry5降级为4后解决了