gmfe / Think

观麦前端团队的官方博客
68 stars 3 forks source link

react-router2 迁移到 react-router4 关注点 #6

Open liyatang opened 7 years ago

liyatang commented 7 years ago

以下mark MA切换react-router4过程中注意点

文档

https://github.com/react-translate-team/react-router-CN https://reacttraining.cn/

使用了两个库 react-router 通用库,web 和 Native 都可用 react-router-dom web用

Router Route

不累赘,看文档和MA代码吧,✌️

Link

// 以前
import {Link} from 'react-router';

// now
import {Link} from 'react-router-dom';

history

service.js 中history的提供方式变了

// 以前
import {hashHistory} from 'react-router';
const history = hashHistory;

// now
import createHashHistory from 'history/createBrowserHistory'
const history = createHashHistory()

query search

query 切到了 search,且search是字符串类型 query => search(obj =>string)

具体文档见 https://github.com/ReactTraining/history

push && replace

// 以前
history.push({
    pathname: '/home',
    query: {
        the: 'query'
    }
})

// now
history.push({
    pathname: '/home',
    search: '?the=query',
})

history.replace 同理,不累赘

props

route提供的props也变更。react-router2 提供 location, history, params,react-router4 提供 location, history, match

其中 location.query (obj) => location.search(string) 需要自己对字符串处理,可引入 query-string

props.params -> props.match.params

兼容

由于 query 和 params 以前业务用的多,一下子切过去有风险,估做了兼容逻辑,保证react-router2的这部分能用,但未来还是会废弃掉的

怎么做的 ?

会对react-router提供的props做加工处理,提供query 和 params,代码见gm-util

import queryString from 'query-string';

export default function processReactRouterProps(props) {
    const newProps = Object.assign({}, props);
    newProps.location.query = queryString.parse(props.location.search);
    newProps.params = props.match.params || {}; // 不止 || 是否有意义
    return newProps;
}

对history的方法做处理

import _ from 'lodash';
import queryString from 'query-string';

function processHistory(history) {
    const _push = history.push;
    const _replace = history.replace;

    history.push = function (one) {
        if (!_.isPlainObject(one)) {
            return _push.apply(this, arguments);
        }

        const o = Object.assign({}, one);

        if (o.query) {
            o.search = queryString.stringify(o.query);
        }

        _push.apply(this, [o]);
    };

    history.replace = function (one) {
        if (!_.isPlainObject(one)) {
            return _replace.apply(this, arguments);
        }

        const o = Object.assign({}, one);

        if (o.query) {
            o.search = queryString.stringify(o.query);
        }

        _replace.apply(this, [o]);
    };

    return history;
}

export default processHistory;
fondadam commented 7 years ago

添加一些没记录到的地方

1. Router/Route 的改变

// V2 or V3
import { Router, Route, hashHistory } from 'react-router';

<Router history={hashHistory}>
  <Route path='/foo' component={Foo} />
  <Route path='/bar' component={Bar} />
</Router>
// V4 Router组件里只能渲染一个组件
import {
    HashRouter as Router,
    Route
} from 'react-router-dom';

<Router>
  <div>
    <Route path='/foo' component={Foo} />
    <Route path='/bar' component={Bar} />
  </div>
</Router>

2. 组件嵌套

// V2 or V3 路由组件嵌套
import { Router, Route, hashHistory } from 'react-router';

<Router history={hashHistory}>
  <Route path='/' component={App}>
    <Route path='foo' component={Foo} />
    <Route path='bar' component={Bar} />
  </Route>
</Router>
// V4 Router 的路由组件嵌套
import {
    HashRouter as Router,
    Route,
    Switch
} from 'react-router-dom';

<Router>
 <Route path="/" component={(props) => (
    <App {...props}>
      <Switch>
        <Route path='/foo' component={Foo} />
        <Route path='/bar' component={Bar} />
      </Switch>
    </App>
  )}/>
</Router>

3. 路由的生命周期

react-router V4中去掉了on****的路由生命周期的钩子,但是你可以在组件中用componentDidMountcomponentWillMount 代替 onEnter,可以用componentWillUpdatecomponentWillReceiveProps代替 onUpdate,你可以用componentWillUnmount 代替 onLeave

svkd9 commented 7 years ago

push之后地址栏url改变但是页面没有刷新,请问你有没有遇到过 import createHashHistory from 'history/createBrowserHistory' const history = createHashHistory()

history.push({ pathname: '/login' })

liyatang commented 7 years ago

@svkd9 情况太多,建议提供简易版demo。 几个思路可以看下: 1 路由设置对了? 2 跳转过去的路由有错?

svkd9 commented 7 years ago

感谢回复,下面是我写的了一个简单的demo https://github.com/svkd9/react-router_v4_demo.git

liyatang commented 7 years ago

@svkd9 sorry,同蒙圈。 不过我换成 createHashHistory 就没问题了。 至于 createBrowserHistory 为啥不行目前还没答案。

yongningfu commented 7 years ago

我来解答一下为何 createHashHistory可以, 但是createBrowserHistory 不行吧! 首先,上面的HashRouter 和BrowserRoute里面都是内含有 history对象的,这一点 @liyatang 也说了。

为何hashHistory可以,但是broserHistory不可以呢,

其实这个问题解释不难,照着逻辑去定位一下就可以了
(ps: router本身是用history事件订阅去触发router组件更新的, 所有关键是寻找如何触发history订阅的事件)

因为window对象有个 hashchange 事件 可以监听哈希的变化
在hashHistory中, 通过window去监听hashchange变化 而去触发history订阅的事件的(即router组件更新)源码 即无论哪个history对象触发的hash变化,都会被window监听到,而且执行路由更新,所以hashRouter是没问题的。

但是浏览器并没有直接监听url变化的事件, 所以broserHistory变化本身只能使用同一个history对象的执行本身的的事件订阅模式去触发组件的更新,当然代码里面还增加了 对window对象的的 hashchange 和 popstate 事件监听

相关源码tips1tips2

@liyatang @svkd9

liyatang commented 7 years ago

豁然开朗,谢谢。 @yongningfu

yongningfu commented 7 years ago

大概浏览了一下你们组件库的代码,确实逻辑处理的还是非常清晰的,能直接封装一套适合公司用的ui, 感觉还是很棒的,你们还招人么? @liyatang

liyatang commented 7 years ago

@yongningfu 加微信 wxliyatang

Statfine commented 7 years ago

@fondadam @liyatang 关于用componentDidMount 或 componentWillMount 代替 onEnter的问题, 如果路由的位置用到onEnter(nextState, replace, callback);因为该方法中的异步如果没有返回,页面就不会渲染。所以有时会将用户的验证放在该方法中的,可是如果改成放到componentWillMount,如果有异步验证并且还没返回的时候,是不是页面就不渲染组建(组件中的请求需要用户验证之后)同时页面还需要特意声明一个state状态

ghost commented 6 years ago

是否我需要js跳转路由的组件,都需要 import createHistory from 'history/createHashHistory' const history = createHistory() 这样不麻烦. 不能用this.出来么

liyatang commented 6 years ago

@AlanZou007 你可以把history保存起来

JesterCheung commented 6 years ago

请问,router@4中,“只有当访问地址和路由匹配时,一个 React component 才会被渲染”。我现在是结合了antd,实现tab标签。而tab标签的切换是不会导致地址栏发生变化的,因此不会渲染组件。有没有解决办法呢?

liyatang commented 6 years ago

@JesterCheung 理下你需求是否这样: tab切换会带动地址栏变化?

那你在切换的时候 history.push(xxxx) 就ok了,把tab的状态当成路由的参数。

eg 路由设置成 xxxxxx/#/A/B?tab=1

切换tab的时候

history.push({
    pathname: 'xxxxxx/', 
    search: '?tab=1'
})

btw,tab一般不建议做成导致地址栏变化。 至于切换tab,组件是否渲染是tab的实现方式问题,属于另外个话题。 可以把代码抛出来讨论。

j710328466 commented 6 years ago
import React, { Component } from 'react';
import { Router, Route, Link, Switch, HashHistory } from 'react-router-dom';
import PropTypes from 'prop-types';
import './app.css'
import Goods from './pages/goods/goods'
import Active from './pages/active/active'
import Detail from './pages/detail/detail'
import Home from './pages/home/home'
import createHistory from 'history/createHashHistory'
const history = createHistory()

class App extends Component {
  constructor () {
    super()
    this.state = {
    }
  }

  render() {
    return (
      <Router history={history}>
        <div className="container"> 
          <div className="siderBar">
            <ul>
              <li className="siderBar_btn"><Link to="/">首页</Link></li>
              <li className="siderBar_btn"><Link to="/goods">商品专区</Link></li>
              <li className="siderBar_btn"><Link to="/detail">商品详情</Link></li>
              <li className="siderBar_btn"><Link to="/active">活动</Link></li>
            </ul>
          </div>

          <div className="content">
            <h1 className="title">解码器1.0</h1> 
            <Route render={({ location }) => {
              return (
                <Switch>
                  <Route path="/" component={Home} />
                  <Route path="/goods" component={Goods} />
                  <Route location={location} path="detail" component={Detail} />
                  <Route location={location} path="active" component={Active} />
                </Switch>
              )
            }} />
            <div className="footer"><img src={require('./assets/logo.png')} /></div>
          </div>
        </div>
      </Router>
    )
  }
}

export default App;

请问我的问题出在哪?url变化,路由不跳转,重复点击的话提示Warning: Hash history cannot PUSH the same path; a new entry will not be added to the history stack warning @ browser.js?e722:49 折腾了我好久,求告知谢谢~