react-router 升级小记

(点击上方公众号,可快速关注)


作者:哆啦P股 

http://div.io/topic/2073


前言


最近将公司项目的 react-router 从 v3 版本升到了 v4 版本,react-router v4 跟 v3 完全不兼容,是一次彻底的重写。这也给升级造成了极大的困难,与其说升级不如说是对 router 层重写。之前我也将项目的 react 从 v15 版本升级到了 v16 版本,相较而言升级 react-router 比升级 react 困难多了。升级过程中踩了不少的坑,也有一些值得分享的点。写成一篇小文,供大家参考。


依赖升级


react-router v4 跟 react 一样拆成了两部分,核心的 react-router 和依运行环境而定的 react-router-dom 或 react-router-native(跟 react-dom 和 react-native 一样)。本文要说的是浏览器环境,也就是 react-router + react-router-dom


先安装依赖(推荐使用 yarn)


yarn add react-router react-router-dom history


为什么要安装 history 后面会解释。


组件外导航与 react-router-redux


之前我们项目中使用了 react-router-redux 你有很多理由使用它,但对于我们来说唯一的理由或者用处就是用于在页面组件之外导航,react-router-redux 让你可以在任何地方通过 dispatch 处理页面跳转,如:store.dispatch(push(‘/’))。因为这个我们就必须使用 react-router-redux 吗?当然不需要,有更简单的办法实现这个需求。所以这次升级我移除了react-router-redux, 写作此文时支持 react-router v4 的 react-router-redux 还处于 v5.0.0-alpha.7 也是原因之一。


还记得之前安装的 history 吗?history 是 react-router 唯二的主要依赖之一,之所以要显式安装,是因为我们要使用它来实现页面组件外导航。以下以 browser history 为例(hash history 和 memory history 都是一样的):


我们不使用 react-router-dom 提供的 BrowserRouter 而是自己实现一个


// history.js

import createHistory from 'history/createBrowserHistory';

 

const history = createHistory();

 

export default history;


// index.js

import React from 'react';

import ReactDOM from 'react-dom';

import { Router } from 'react-router';

import history from './history';

import App from './app';

 

ReactDOM.render(

  <Router history={history}>

    <App />

  </Router>,

  document.getElementById('app')

);


搞定!就这么简单,这样在任何地方只要引用 history 就可以使用它进行导航操作,如 history.push(‘/’),更多使用方式请参考 history 文档。其实 react-router-dom 的 BrowserRouter 跟我们做了同样的事,区别在于我们这么做能把 history 暴露出来。这个 history 就是页面组件 props 里面的 history 自然也就能做同样的事情。


静态配置


react-router v3 是面向配置的,组件写法只是一种语法糖。而 react-router v4 是完全面向组件的,提供的 Route Switch 等都是真正的组件。这也就导致只能按组件的方式写路由,不能写配置。但是 v3 那样的配置确实有一些方便之处,如统一管理、使用方便等。


多亏 JSX 灵活的语法,我们依然有办法按配置的方式写 react-router v4 的路由。


// routes.js

import Home form './home';

import About form './about';

import Help form './help';

 

export default [{

  path: '/',

  exact: true,

  component: Home

}, {

  path: '/about',

  component: About

}, {

  path: '/help',

  component: Help

}];


// app.js

import React from 'react';

import { Switch, Route } from 'react-router';

import routes from './routes';

import NotFound from './not-found';

 

class App extends React.Component {

  render() {

    return (

      <Switch>

        {routes.map((route, i) => <Route key={i} exact={!!route.exact} path={route.path} component={route.component} />)}

        <Route component={NotFound} />

      </Switch>

    );

  }

}

 

export default App;


这样我们就用配置的方式写出了面向组件的路由,兼顾两者的优点。如果有嵌套路由需求,可以参考官方示例。官方也提供了一个 react-router-config, 不过我没有使用,一来觉得没必要,二来写作此文时它还处于 v1.0.0-beta.4 版本。


异步组件与 Code Splitting


Web 应用最大的一个优势就是不必下载整个应用,只用下载需要的部分就可以使用。要达到这样的目标,就需要对代码进行分片,异步加载组件。可惜 react-router v4 没有像 v3 一样提供加载异步组件的接口。这部分工作就需要我们自己来处理。


我们可以创建一个高阶组件 Bundle,专门用来加载异步组件。


// bundle.js

import React from 'react';

 

class Bundle extends React.Component {

  constructor(props) {

    super(props);

    this.state = { Component: null };

    props.load().then(Component => this.setState({ Component: Component.default }));

  }

  render() {

    const { load, ...props } = this.props;

    const Component = this.state.Component;

    return Component ? <Component {...props}/> : null;

  }

}

 

export default Bundle;


然后修改一下 routes.js


// routes.js

import React from 'react';

import Bundle from './bundle';

 

export default [{

  path: '/',

  exact: true,

  component(props) {

    // 这里的 component 函数也是一个高阶组件

    return <Bundle {...props} load={() => import('./home')} />;

  }

}, {

  path: '/about',

  component(props) {

    return <Bundle {...props} load={() => import('./about')} />;

  }

}, {

  path: '/help',

  component(props) {

    return <Bundle {...props} load={() => import('./help')} />;

  }

}];


这样每个页面都会打包成单独的 JS,访问相应页面才会去异步加载对应的组件。这样也可以做精细化缓存控制。


需要注意的是 import() 语法在写作本文时还处于 Stage 2 的状态,需给 Babel 添加 syntax-dynamic-import 插件才能正常工作,另外需 webpack 2 及以上才支持。


查询参数


因为各种原因 react-router v4 不再解析 ?key=value 这样的 URL 的查询参数,页面组件 props.location 中只有 search 字符串。这跟 v3 不兼容,而且很不方便。我们有办法兼容一下吗?当然有,这时候之前写的 histroy.js 又有新的用处了。


// history.js

import qs from 'qs';

import createHistory from 'history/createBrowserHistory';

 

function addQuery(history) {

  const location = history.location;

  history.location = { ...location, query: qs.parse(location.search, { ignoreQueryPrefix: true }) };

}

 

const history = createHistory();

 

addQuery(history);

 

export const unlisten = history.listen(() => {

  // 每次页面跳转都会执行

  addQuery(history);

});

 

export default history;


这样我们就能在页面组件 props.location.query 拿到解析好的 URL 查询参数了,跟 v3 完美兼容。还有个额外的好处是在任何地方引用 history 都可以拿到解析好的 URL 查询参数。需要注意的是,在 history 的设计中,history 对象是 Mutable 的,所以我们可以直接修改 history。但是 history.location 是 Immutable 的,所以我们要确保每一个 location 对象都是全新的。


搭配 Redux


react-router v4 跟 redux 搭配有一个大坑(mobx 应该也有同样的问题),详情请看这篇文章,这里就不再赘述。简单来说,如果一个组件用 redux 的 connect 包装过,又️不是 Route 的子组件,那么 history 的变更就不会触发这个组件的更新,它的子组件自然也不会更新。比如应用的根组件(上文的 App)。


解决方案也很简单,可以用 react-router v4 提供的 withRouter 再包装一遍:withRouter(connect(…)(App)),或者让 App 做为 Router 的子组件,原理都一样。我采用的后者。


// app.js

import React from 'react';

import { connect } from 'react-redux';

import { Switch, Route } from 'react-router';

import routes from './routes';

import NotFound from './not-found';

 

class App extends React.Component {

  render() {

    return (

      <Switch>

        {routes.map((route, i) => <Route key={i} exact={!!route.exact} path={route.path} component={route.component} />)}

        <Route component={NotFound} />

      </Switch>

    );

  }

}

 

function mapStateToProps(state) {

  return {

    someState: state.someState

  };

}

 

export default connect(mapStateToProps)(App);


// index.js

import React from 'react';

import ReactDOM from 'react-dom';

import { Provider } from 'react-redux';

import { Router, Route } from 'react-router';

import store from './store';

import history from './history';

import App from './app';

 

ReactDOM.render(

  <Provider store={store}>

    <Router history={history}>

      <Route component={App} /> {/* 没有 path 就会匹配所有路由 */}

    </Router>

  </Provider>,

  document.getElementById('app')

);


最后


不得不说升级 react-router 很困难,坑也很多。但是把坑一个个填完,最终完美升级也是一件很有意思,很有成就感的事。希望这篇文章能对你有所帮助。


另外完整的 Demo 请戳我的 GitHub,喜欢的话点个 Star 吧 :P



觉得本文对你有帮助?请分享给更多人

关注「前端大全」,提升前端技能

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园整体解决方案是响应国家教育信息化政策,结合教育改革和技术创新的产物。该方案以物联网、大数据、人工智能和移动互联技术为基础,旨在打造一个安全、高效、互动且环保的教育环境。方案强调从数字化校园向智慧校园的转变,通过自动数据采集、智能分析和按需服务,实现校园业务的智能化管理。 方案的总体设计原则包括应用至上、分层设计和互联互通,确保系统能够满足不同用户角色的需求,并实现数据和资源的整合与共享。框架设计涵盖了校园安全、管理、教学、环境等多个方面,构建了一个全面的校园应用生态系统。这包括智慧安全系统、校园身份识别、智能排课及选课系统、智慧学习系统、精品录播教室方案等,以支持个性化学习和教学评估。 建设内容突出了智慧安全和智慧管理的重要性。智慧安全管理通过分布式录播系统和紧急预案一键启动功能,增强校园安全预警和事件响应能力。智慧管理系统则利用物联网技术,实现人员和设备的智能管理,提高校园运营效率。 智慧教学部分,方案提供了智慧学习系统和精品录播教室方案,支持专业级学习硬件和智能化网络管理,促进个性化学习和教学资源的高效利用。同时,教学质量评估中心和资源应用平台的建设,旨在提升教学评估的科学性和教育资源的共享性。 智慧环境建设则侧重于基于物联网的设备管理,通过智慧教室管理系统实现教室环境的智能控制和能效管理,打造绿色、节能的校园环境。电子班牌和校园信息发布系统的建设,将作为智慧校园的核心和入口,提供教务、一卡通、图书馆等系统的集成信息。 总体而言,智慧校园整体解决方案通过集成先进技术,不仅提升了校园的信息化水平,而且优化了教学和管理流程,为学生、教师和家长提供了更加便捷、个性化的教育体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值