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 相关文章推荐
javascript 时间比较实现代码
Oct 28 Javascript
基于IE下ul li 互相嵌套时的bug,排查,解决过程以及心得介绍
May 07 Javascript
javascript实现节点(div)名称编辑
Dec 17 Javascript
Javascript中的方法和匿名方法实例详解
Jun 13 Javascript
JavaScritp添加url参数并将参数加入到url中及更改url参数的方法
Oct 26 Javascript
JavaScript为事件句柄绑定监听函数实例详解
Dec 15 Javascript
easyui validatebox验证
Apr 29 Javascript
AngularJS ng-template寄宿方式用法分析
Nov 07 Javascript
vue 如何添加全局函数或全局变量以及单页面的title设置总结
Jun 01 Javascript
浅谈Vue 初始化性能优化
Aug 31 Javascript
bootstrap自定义样式之bootstrap实现侧边导航栏功能
Sep 10 Javascript
JS面向对象编程基础篇(二) 封装操作实例详解
Mar 03 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
php debug 安装技巧
2011/04/30 PHP
php使用array_rand()函数从数组中随机选择一个或多个元素
2014/04/28 PHP
php数组转成json格式的方法
2015/03/09 PHP
无需数据库在线投票调查php代码
2016/07/20 PHP
laravel学习教程之关联模型
2016/07/30 PHP
PHP实现用户登录的案例代码
2018/05/10 PHP
jQuery学习笔记之控制页面实现代码
2012/02/27 Javascript
javascript学习笔记之10个原生技巧
2014/05/21 Javascript
jQuery扁平化风格下拉框美化插件FancySelect使用指南
2015/02/10 Javascript
JavaScript基础知识之方法汇总结
2016/01/24 Javascript
JS实现为排序好的字符串找出重复行的方法
2016/03/02 Javascript
js 声明数组和向数组中添加对象变量的简单实例
2016/07/28 Javascript
Bootstrap基本样式学习笔记之表格(2)
2016/12/07 Javascript
JavaScript实现时钟滴答声效果
2017/01/29 Javascript
JS实现移动端整屏滑动的实例代码
2017/11/10 Javascript
基于 Vue 实现一个酷炫的 menu插件
2017/11/14 Javascript
webpack4 css打包压缩问题的解决
2018/05/18 Javascript
使用ng-packagr打包Angular的方法示例
2018/09/21 Javascript
vue3为什么要用proxy替代defineProperty
2020/10/19 Javascript
Python实现批量修改文件名实例
2015/07/08 Python
Python 正则表达式入门(中级篇)
2016/12/07 Python
python 实现在shell窗口中编写print不向屏幕输出
2020/02/19 Python
Python求两个字符串最长公共子序列代码实例
2020/03/05 Python
Python 测试框架unittest和pytest的优劣
2020/09/26 Python
ALLSAINTS英国官网:伦敦新锐潮流品牌
2016/09/19 全球购物
自然健康的概念:Natural Healthy Concepts
2020/01/26 全球购物
巴西葡萄酒商店:Divvino
2020/02/22 全球购物
2014年元旦联欢会活动策划方案
2014/02/16 职场文书
《小山羊和小灰兔》教学反思
2014/02/19 职场文书
反洗钱宣传活动总结
2014/08/26 职场文书
2014年度工作总结报告
2014/12/15 职场文书
关于学习的决心书
2015/02/05 职场文书
环卫处个人工作总结
2015/03/04 职场文书
2015年街道除四害工作总结
2015/05/15 职场文书
CDPR谈《巫师》新作用虚幻5原因 称不会为Epic独占
2022/04/06 其他游戏
5个实用的JavaScript新特性
2022/06/16 Javascript