React-router 4 按需加载的实现方式及原理详解


Posted in Javascript onMay 25, 2017

React-router 4

介绍了在router4以后,如何去实现按需加载Component,在router4以前,我们是使用getComponent的的方式来实现按需加载的,router4中,getComponent方法已经被移除,下面就介绍一下react-router4是入围和来实现按需加载的。

1.router3的按需加载方式

route3中实现按需加载只需要按照下面代码的方式实现就可以了。

const about = (location, cb) => {
  require.ensure([], require => {
    cb(null, require('../Component/about').default)
  },'about')
}

//配置route
<Route path="helpCenter" getComponent={about} />

2.router4按需加载方式(three steps)

one step:

创建Bundle.js文件,这个文件其实是个通过bundle-loader包装后的组件来使用,下面会具体讲这个东西。

import React from 'react';
import PropTypes from 'prop-types';

class Bundle extends React.Component {
 state = {
  // short for "module" but that's a keyword in js, so "mod"
  mod: null
 }

 componentWillMount() {
  // 加载初始状态
  this.load(this.props);
 }

 componentWillReceiveProps(nextProps) {
  if (nextProps.load !== this.props.load) {
   this.load(nextProps);
  }
 }

 load(props) {
  // 重置状态
  this.setState({
   mod: null
  });
  // 传入组件的组件
  props.load((mod) => {
   this.setState({
    // handle both es imports and cjs
    mod: mod.default ? mod.default : mod
   });
  });
 }

 render() {
  // if state mode not undefined,The container will render children
  return this.state.mod ? this.props.children(this.state.mod) : null;
 }
}

Bundle.propTypes = {
 load: PropTypes.func,
 children: PropTypes.func
};

export default Bundle;

second step:

import aContainer from 'bundle-loader?lazy!./containers/A'

const A = (props) => (
 <Bundle load={aContainer}>
   //这里只是给this.props.child传一个方法,最后在Bundle的render里面调用
  {(Container) => <Container {...props}/>}
 </Bundle>
)

third step:

render() {
  return (
   <div>
    <h1>Welcome!</h1>
    <Route path="/about" component={About}/>
    <Route path="/dashboard" component={A}/>
   </div>
  )
 }

3.router4按需加载方方式解析

(1).首先解释一下按需加载,通俗的将就是我当前的location在Home,那么我只应该加载Home的东西,而不应该去加载About等等其他的。

(2).Bundle.js这个文件的作用

先看这段代码:

module.exports = function (cb) {
  __webpack_require__.e/* require.ensure */(2).then((function (require) {
    cb(__webpack_require__(305));
  }).bind(null, __webpack_require__)).catch(__webpack_require__.oe);
};

这里是我们通过import loadDashboard from 'bundle-loader?lazy!./containers/A'这种方式引入的container控件。我们使用了bundle-loader将A的源码转化成了上面的代码,具体实现大家可以看bundle-loader源码,代码很少。

上面说到Bundle.js其实就使用来处理这个文件的,这个文件需要一个callback的参数,在Bundle的load方法中,我们会设置这个callback,当路由要调到A Container这里的时候,就回去加载A Container,然后调用这个callback,这个callback会调用setState方法,将我们之前传入的load设置给mod,然后渲染出来。

4.webpack进行bundle-loader统一配置

这里匹配的是src/routers/下面的containers文件夹下面所有的js文件,包括二级目录。

{
   // 匹配routers下面所有文件
   // ([^/]+)\/?([^/]*) 匹配xxx/xxx 或者 xxx
   test: /containers\/([^/]+)\/?([^/]*)\.jsx?$/,
   include: path.resolve(__dirname, 'src/routers/'),
   // loader: 'bundle-loader?lazy'
   loaders: ['bundle-loader?lazy', 'babel-loader']
  }

5.部分源码

1.bundle-loader的源码

var loaderUtils = require("loader-utils");

module.exports = function() {};
module.exports.pitch = function(remainingRequest) {
  this.cacheable && this.cacheable();
  var query = loaderUtils.getOptions(this) || {};
  if(query.name) {
    var options = {
      context: query.context || this.options.context,
      regExp: query.regExp
    };
    var chunkName = loaderUtils.interpolateName(this, query.name, options);
    var chunkNameParam = ", " + JSON.stringify(chunkName);
  } else {
    var chunkNameParam = '';
  }
  var result;
  if(query.lazy) {
    result = [
      "module.exports = function(cb) {\n",
      "  require.ensure([], function(require) {\n",
      "    cb(require(", loaderUtils.stringifyRequest(this, "!!" + remainingRequest), "));\n",
      "  }" + chunkNameParam + ");\n",
      "}"];
  } else {
    result = [
      "var cbs = [], \n",
      "  data;\n",
      "module.exports = function(cb) {\n",
      "  if(cbs) cbs.push(cb);\n",
      "  else cb(data);\n",
      "}\n",
      "require.ensure([], function(require) {\n",
      "  data = require(", loaderUtils.stringifyRequest(this, "!!" + remainingRequest), ");\n",
      "  var callbacks = cbs;\n",
      "  cbs = null;\n",
      "  for(var i = 0, l = callbacks.length; i < l; i++) {\n",
      "    callbacks[i](data);\n",
      "  }\n",
      "}" + chunkNameParam + ");"];
  }
  return result.join("");
}

/*
Output format:

  var cbs = [],
    data;
  module.exports = function(cb) {
    if(cbs) cbs.push(cb);
    else cb(data);
  }
  require.ensure([], function(require) {
    data = require("xxx");
    var callbacks = cbs;
    cbs = null;
    for(var i = 0, l = callbacks.length; i < l; i++) {
      callbacks[i](data);
    }
  });

*/

2.A的源码

import React from 'react';
import PropTypes from 'prop-types';
import * as reactRedux from 'react-redux';
import BaseContainer from '../../../containers/ReactBaseContainer';

class A extends BaseContainer {
 constructor(props) {
  super(props);
  this.renderCustom = function renderCustom() {
   return (
    <div >
     Hello world In A
    </div>
   );
  };
 }
 render() {
  // 返回父级view
  return super.render();
 }
}

A.propTypes = {
 dispatch: PropTypes.func,
};

function mapStateToProps(state) {
 return { state };
}

export default reactRedux.connect(mapStateToProps)(A);

3.route.js的源码

import React from 'react';
import { BrowserRouter, Switch, Link } from 'react-router-dom';
import { Route } from 'react-router';
import PostContainer from '../containers/PostsContainer';
// 设置trunk文件的名字 the basename of the resource
import aContainer from './containers/A';
import bContainer from './containers/B';
import cContainer from './containers/C';
import Bundle from '../utils/Bundle';

const A = () => (
 <Bundle load={aContainer}>
  {Component => <Component />}
 </Bundle>
)

const app = () =>
 <div>
  {/* path = "/about" */}
  {/* "/about/" 可以,但"/about/1"就不可以了 exact 配置之后,需要路径绝对匹配,多个斜杠没有关系,这里直接在浏览器里面设置还有问题*/}
  {/* path = "/about/" */}
  {/* "/about/1" 可以,但"/about"就不可以了 用了strict,path要大于等于的关系,少一个斜杠都不行 */}
  {/* exact 和 strick 都用了就必须一模一样,连斜杠都一样 */}
  <Link to="/about/"> Link to about</Link>
  <Route path="/" component={PostContainer} />
  <Route path="/about/" component={A} />
  {/* <Route path="/home" component={B} />
  <Route component={C} /> */}
 </div>
;
export default function () {
 // 用来判断本地浏览器是否支持刷新
 const supportsHistory = 'pushState' in window.history;
 return (
  <BrowserRouter forceRefresh={!supportsHistory} keyLength={12}>
   <div>
    {app()}
   </div>
  </BrowserRouter>

 );
}

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

Javascript 相关文章推荐
如何简单地用YUI做JavaScript动画
Mar 10 Javascript
IE/FireFox具备兼容性的拖动代码
Aug 13 Javascript
超级有用的13个基于jQuery的内容滚动插件和教程
Jul 31 Javascript
javascript操作table(insertRow,deleteRow,insertCell,deleteCell方法详解)
Dec 16 Javascript
jQuery实现的仿百度分页足迹效果代码
Oct 30 Javascript
详解javascript中的事件处理
Nov 06 Javascript
js实现带缓动动画的导航栏效果
Jan 16 Javascript
vue增删改查的简单操作
Jul 15 Javascript
Vue.js实现的计算器功能完整示例
Jul 11 Javascript
ionic使用angularjs表单验证(模板验证)
Dec 12 Javascript
Node.js实现用户评论社区功能(体验前后端开发的乐趣)
May 09 Javascript
JS学习笔记之数组去重实现方法小结
May 29 Javascript
node.js操作mysql简单实例
May 25 #Javascript
基于vue实现swipe分页组件实例
May 25 #Javascript
Javascript 实现匿名递归的实例代码
May 25 #Javascript
Kotlin学习第一步 kotlin语法特性
May 25 #Javascript
jQuery Masonry瀑布流布局神器使用详解
May 25 #jQuery
jQuery模拟实现天猫购物车动画效果实例代码
May 25 #jQuery
jquery.masonry瀑布流效果
May 25 #jQuery
You might like
精致的人儿就要挑杯子喝咖啡
2021/03/03 冲泡冲煮
PHP 写文本日志实现代码
2010/05/18 PHP
PHP附件下载中文名称乱码的解决方法
2015/12/17 PHP
php自动提交表单的方法(基于fsockopen与curl)
2016/05/09 PHP
Javascript remove 自定义数组删除方法
2009/10/20 Javascript
比较详细的关于javascript 解析json的代码
2009/12/16 Javascript
js生成随机数之random函数随机示例
2013/12/20 Javascript
node.js实现多图片上传实例
2014/06/03 Javascript
javascript中的__defineGetter__和__defineSetter__介绍
2014/08/15 Javascript
javascript 回调函数详解
2014/11/11 Javascript
javascript 闭包详解
2015/02/15 Javascript
jquery仿百度百科底部浮动导航特效
2015/08/08 Javascript
Angular 应用技巧总结
2016/09/14 Javascript
vuejs指令详解
2017/02/07 Javascript
javascript滚轮事件基础实例讲解(37)
2017/02/14 Javascript
layui选项卡效果实现代码
2017/05/19 Javascript
jQuery插件jsonview展示json数据
2018/05/26 jQuery
JS获取浏览器地址栏的多个参数值的任意值实例代码
2018/07/24 Javascript
vue-cli在 history模式下的配置详解
2019/11/26 Javascript
微信小程序如何实现radio单选框单击打勾和取消
2020/01/21 Javascript
Vue+penlayers实现多边形绘制及展示
2020/12/24 Vue.js
[02:21]十步杀一人,千里不留行——DOTA2全新英雄天涯墨客展示
2018/08/29 DOTA
python实现多线程暴力破解登陆路由器功能代码分享
2015/01/04 Python
python查询sqlite数据表的方法
2015/05/08 Python
解决Python运行文件出现out of memory框的问题
2018/12/03 Python
python面向对象实现名片管理系统文件版
2019/04/26 Python
python3 实现爬取TOP500的音乐信息并存储到mongoDB数据库中
2019/08/24 Python
CSS3 Media Queries(响应式布局可以让你定制不同的分辨率和设备)
2013/06/06 HTML / CSS
eDreams巴西:廉价机票,酒店优惠和度假套餐
2017/04/14 全球购物
Amara德国:家居饰品、设计师品牌和豪华礼品
2019/05/20 全球购物
公司活动邀请函
2014/01/24 职场文书
家长评语和期望
2014/02/10 职场文书
小学一年级学生评语
2014/04/22 职场文书
2014年远程教育工作总结
2014/12/09 职场文书
致运动员的广播稿
2015/08/19 职场文书
Mysql数据库值的添加、修改、删除及清空操作实例
2021/06/20 MySQL