详解react-router如何实现按需加载


Posted in Javascript onJune 15, 2017

注:本文使用的 react-router 版本为 2.8.1

React Router 是一个非常出色的路由解决方案,同时也非常容易上手。但是当网站规模越来越大的时候,首先出现的问题是 Javascript 文件变得巨大,这导致首页渲染的时间让人难以忍受。实际上程序应当只加载当前渲染页所需的 JavaScript,也就是大家说的“代码分拆" — 将所有的代码分拆成多个小包,在用户浏览过程中按需加载。

所得到的效果是:

以前是这样(23333,我真不是故意的。。)

详解react-router如何实现按需加载

现在是这样:

详解react-router如何实现按需加载

实际上就是将一个大 javascript 文件拆分成了若干个 chunk file。

下面是改造过程

Webpack 配置

首先在 webpack.config.js output 内加上 chunkFilename

output: {
  path: path.join(__dirname, '/../dist/assets'),
  filename: 'app.js',
  publicPath: defaultSettings.publicPath,
  // 添加 chunkFilename
  chunkFilename: '[name].[chunkhash:5].chunk.js',
},

name 是在代码里为创建的 chunk 指定的名字,如果代码中没指定则 webpack 默认分配 id 作为 name。

chunkhash 是文件的 hash 码,这里只使用前五位。

添加首页

以前你的路由大概应该是这样的:(作为需要按需加载的大型应用,路由肯定是相当复杂,这里只列举部分路由举例)

ReactDOM.render(
 (
  <Router history={browserHistory}>
   {/* 主页 */}
   <Route path="/" component={App}>
    {/* 默认 */}
    <IndexRoute component={HomePage} />

    {/* baidu */}
    <Route path="/baidu" component={BaiduPage}>
     <Route path="result" component={BaiduResultPage} />
     <Route path="frequency" component={BaiduFrequencyPage} />
    </Route>

    {/* 404 */}
    <Route path='/404' component={NotFoundPage} />
    
    {/* 其他重定向到 404 */}
    <Redirect from='*' to='/404' />
   </Route>
  </Router>
 ), document.getElementById('app')
);

按需加载之后,我们需要让路由动态加载组件,需要将 component 换成 getComponent。首先将路由拆出来(因为路由庞大之后全部写在一起会很难看),创建一个根路由 rootRoute:

const rootRoute = {
 path: '/',
 indexRoute: {
  getComponent(nextState, cb) {
   require.ensure([], (require) => {
    cb(null, require('components/layer/HomePage'))
   }, 'HomePage')
  },
 },
 getComponent(nextState, cb) {
  require.ensure([], (require) => {
   cb(null, require('components/Main'))
  }, 'Main')
 },
 childRoutes: [
  require('./routes/baidu'),
  require('./routes/404'),
  require('./routes/redirect')
 ]
}

ReactDOM.render(
 (
  <Router
   history={browserHistory}
   routes={rootRoute}
   />
 ), document.getElementById('app')
);

history 不变,在 Router 中添加 routes 属性,将创建的路由传递进去。

这里有四个属性:

path

将匹配的路由,也就是以前的 path。

getComponent

对应于以前的 component 属性,但是这个方法是异步的,也就是当路由匹配时,才会调用这个方法。

这里面有个 require.ensure 方法

require.ensure(dependencies, callback, chunkName)

这是 webpack 提供的方法,这也是按需加载的核心方法。第一个参数是依赖,第二个是回调函数,第三个就是上面提到的 chunkName,用来指定这个 chunk file 的 name。

如果需要返回多个子组件,则使用 getComponents 方法,将多个组件作为一个对象的属性通过 cb 返回出去即可。这个在官方示例也有,但是我们这里并不需要,而且根组件是不能返回多个子组件的,所以使用 getComponent。

indexRoute

用来设置主页,对应于以前的 <IndexRoute>。

注意这里的 indexRoute 写法, 这是个对象,在对象里面使用 getComponent。

childRoutes

这里面放置的就是子路由的配置,对应于以前的子路由们。我们将以前的 /baidu、/404 和 * 都拆了出来,接下来将分别为他们创建路由配置。

路由控制

上面的childRoutes 里面,我们 require 了三个子路由,在目录下创建 routes 目录,将这三个路由放置进去。

routes/
├── 404
│  └── index.js
├── baidu
│  ├── index.js
│  └── routes
│    ├── frequency
│    │  └── index.js
│    └── result
│      └── index.js
└── redirect
  └── index.js

和 rootRoute 类似,里面的每个 index.js 都是一个路由对象:

/404/index.js

module.exports = {
 path: '404',

 getComponent(nextState, cb) {
  require.ensure([], (require) => {
   cb(null, require('components/layer/NotFoundPage'))
  }, 'NotFoundPage')
 }
}

/baidu/index.js

module.exports = {
 path: 'baidu',

 getChildRoutes(partialNextState, cb) {
  require.ensure([], (require) => {
   cb(null, [
    require('./routes/result'),
    require('./routes/frequency')
   ])
  })
 },

 getComponent(nextState, cb) {
  require.ensure([], (require) => {
   cb(null, require('components/layer/BaiduPage'))
  }, 'BaiduPage')
 }
}

/baidu/routes/frequency/index.js

module.exports = {
 path: 'frequency',

 getComponent(nextState, cb) {
  require.ensure([], (require) => {
   cb(null, require('components/layer/BaiduFrequencyPage'))
  }, 'BaiduFrequencyPage')
 }
}

举这几个例子应该就差不多了,其他都是一样的,稍微有点特别的是 redirect。

设置 Redirect

之前我们在根路由下是这么设置重定向的:

<Router history={browserHistory}>
   <Route path="/" component={App}>
    {/* home */}
    <IndexRoute component={HomePage} />

    <Route path="/baidu" component={BaiduPage}>
     <Route path="result" component={BaiduResultPage} />
     <Route path="frequency" component={BaiduFrequencyPage} />
    </Route>

    <Route path='/404' component={NotFoundPage} />
    {/* 如果都不匹配,重定向到 404 */}
    <Redirect from='*' to='/404' />
   </Route>
  </Router>

当改写之后,我们需要把这个重定向的路由单独拆出来,也就是 * 这个路由,我们上面已经为他创建了一个 redirect 目录。这里使用到 onEnter 方法,然后在这个方法里改变路由状态,调到另外的路由,实现 redirect :

/redirect/index.js

module.exports = {
 path: '*',
 onEnter: (_, replaceState) => replaceState(null, "/404")
}

The root route must render a single element

跟着官方示例和上面码出来之后,可能页面并没有渲染出来,而是报 The root route must render a single element 这个异常,这是因为 module.exports 和 ES6 里的 export default 有区别。

如果你是使用 es6 的写法,也就是你的组件都是通过 export default 导出的,那么在 getComponent 方法里面需要加入 .default。

getComponent(nextState, cb) {
  require.ensure([], (require) => {
   // 在后面加 .default
   cb(null, require('components/layer/ReportPage')).default
  }, 'ReportPage')
}

如果你是使用 CommonJS 的写法,也就是通过 module.exports 导出的,那就无须加 .default 了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
YUI的Tab切换实现代码
Apr 11 Javascript
有关DOM元素与事件的3个谜题
Nov 11 Javascript
解决3.01版的jquery.form.js中文乱码问题的解决方法
Mar 08 Javascript
Js 时间函数getYear()的使用问题探讨
Apr 01 Javascript
查找页面中所有类为test的结点的方法
Mar 28 Javascript
VUEJS实战之构建基础并渲染出列表(1)
Jun 13 Javascript
js 判断数据类型的几种方法
Jan 13 Javascript
又一款MVVM组件 构建自己的Vue组件(2)
Mar 13 Javascript
JS简单获取当前日期和农历日期的方法
Apr 17 Javascript
JS+WCF实现进度条实时监测数据加载量的方法详解
Dec 19 Javascript
详解js中的原型,原型对象,原型链
Jul 16 Javascript
javascript实现倒计时关闭广告
Feb 09 Javascript
jQuery实现 RadioButton做必选校验功能
Jun 15 #jQuery
bootstrap daterangepicker汉化以及扩展功能
Jun 15 #Javascript
validationEngine 表单验证插件使用实例代码
Jun 15 #Javascript
JS 设置Cookie 有效期 检测cookie
Jun 15 #Javascript
详解react如何在组件中获取路由参数
Jun 15 #Javascript
react-router JS 控制路由跳转实例
Jun 15 #Javascript
Vue路由跳转问题记录详解
Jun 15 #Javascript
You might like
PHP与javascript的两种交互方式
2006/10/09 PHP
从零开始学YII2框架(一)通过Composer安装Yii2框架
2014/08/20 PHP
在Yii2中使用Pjax导致Yii2内联脚本载入失败的原因分析
2016/03/06 PHP
php UNIX时间戳用法详解
2017/02/16 PHP
懒就要懒到底——鼠标自动点击(含时间判断)
2007/02/20 Javascript
javascript dom 基本操作小结
2010/04/11 Javascript
关于jQuery参考实例2.0 用jQuery选择元素
2013/04/07 Javascript
DOM节点删除函数removeChild()用法实例
2015/01/12 Javascript
使用JavaScript 实现的人脸检测
2015/03/24 Javascript
微信js-sdk分享功能接口常用逻辑封装示例
2016/10/13 Javascript
老生常谈的跨域处理
2017/01/11 Javascript
简单实现nodejs上传功能
2017/01/14 NodeJs
vue模板语法-插值详解
2017/03/06 Javascript
vue项目中jsonp跨域获取qq音乐首页推荐问题
2018/05/30 Javascript
jquery登录的异步验证操作示例
2019/05/09 jQuery
微信小程序云开发如何实现数据库自动备份实现
2019/08/16 Javascript
微信小程序事件 bindtap bindinput代码实例
2019/08/26 Javascript
JavaScript eval()函数定义及使用方法详解
2020/07/07 Javascript
Flask框架学习笔记(一)安装篇(windows安装与centos安装)
2014/06/25 Python
Tornado Web服务器多进程启动的2个方法
2014/08/04 Python
Python通过正则表达式选取callback的方法
2015/07/18 Python
为什么Python中没有&quot;a++&quot;这种写法
2018/11/27 Python
django数据库自动重连的方法实例
2019/07/21 Python
详细分析Python垃圾回收机制
2020/07/01 Python
英国著名国际平价时尚男装品牌:Topman
2016/08/27 全球购物
美国设计师精美珠宝购物网:Netaya
2016/08/28 全球购物
在家更换处方镜片:Lensabl
2019/05/01 全球购物
大课间体育活动方案
2014/03/12 职场文书
小学生2014国庆节演讲稿:祖国在我心中
2014/09/21 职场文书
建党伟业电影观后感
2015/06/01 职场文书
预备党员党支部意见
2015/06/02 职场文书
逃出克隆岛观后感
2015/06/09 职场文书
2007年老电脑安装win11会怎么样? 网友实测win11在老电脑运行良好
2021/11/21 数码科技
解析MySQL索引的作用
2022/03/03 MySQL
通过T-SQL语句创建游标与实现数据库加解密功能
2022/03/16 SQL Server
win10双系统怎么删除一个系统?win10电脑有两个系统删除一个的操作方法
2022/07/15 数码科技