React高阶开发-代码分离

最新更新时间:2019年1月26日23:30:56

《猛戳-查看个人博客地图-总有你意想不到的惊喜》

本文内容:代码分离、资源按需加载javascript

原由

当项目太大时,单页面应用打包成单个bundle包,若是不进行代码分离,首屏会加载没有用到的资源,严重影响首屏加载时间。html

概述

代码分离方案分为两个层面:
  • webpack打包工具
  • react框架自己

一、webpack打包过程的代码分离方案

webpack提供的有三种经常使用的代码分离方法:click here前端

  • 入口起点:使用 entry 配置手动地分离代码。
  • 防止重复:使用 SplitChunks 去重和分离 chunk。
  • 动态导入:经过模块的内联函数调用来分离代码。

1.一、入口起点

多入口打包输出多模块,以下:在entry配置了三个入口,分别是app、vendor、jqueryjava

打包入口分析
  • app 表明业务代码
  • vendor 表明react生态代码,能够进一步细分为:react 和 redux
  • jquery 表明第三方js类库
  • common 表明公共方法(函数)

4个入口,会生成7个js文件,包括4个入口的独立压缩文件和3个关联文件(关联文件:app~ vendor、app~ jquery、app~ common),从此在常规开发中,3个类库文件(vendor、jquery、common)和3个关联文件不会常常改变,生成的chunkhash值不变,项目升级只更新app包,这个业务代码的app包很是小。react

打包出口分析
  • filename 是4个对应入口文件的输出名称
  • chunkFilename 对应3个关联文件的输出名称
module.exports = {
	entry: {
		app:'./src/index.js', // 将 第三方依赖 单独打包
		vendor: [
			'react',
			'react-dom',
			//'react-router',
			'react-router-dom',
			'react-router-redux',
			'redux',
			'react-redux',
		],
		jquery:["jquery"],
		common:['./public/common-module']
	},
	output: {
		path: path.join(__dirname, '../dist'),
		filename: '[name].[chunkhash:8].bundle.js',//当文件没有发生改变chunkhash不变
		chunkFilename: '[name].[chunkhash].js',//非入口(non-entry) chunk 文件(关联文件)的名称
	}
};

注意:此处 react-router 和 react-router-dom 库,二选一,react-router-dom 库包含 react-router 全部内容jquery

遗留问题和陷阱:4个对应入口文件app vendor jquery common中,若是 chunks 之间包含重复的模块,那些重复模块都会被引入到各个 bundle 中,形成重复引用webpack

1.二、防止重复

好比按照入口文件进行拆分时,两个模块都引入了jquery,但jquery没有按照入口拆分,会形成多个模块加载jquery

使用 SplitChunksPlugin 插件来移除重复的模块web

module.exports = {
	optimization: {
		splitChunks: {
			chunks: 'all'
		}
	}
};

1.三、动态导入

  • ES6 的 import() 语法方案一:采用asyncComponent函数导入本地本身封装的模块
import React, { Component } from "react";

export default function asyncComponent(importComponent) {
  class AsyncComponent extends Component {
    constructor(props) {
      super(props);

      this.state = {
        component: null
      };
    }

    async componentDidMount() {
      const { default: component } = await importComponent();

      this.setState({
        component: component
      });
    }

    render() {
      const C = this.state.component;

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

  return AsyncComponent;
}

const AsyncHome = asyncComponent(() => import("./containers/Home"));
<Route path="/" exact component={AsyncHome} />
  • ES6 的 import() 语法方案二:采用import()导入npm包模块
import React, { Component } from 'react';

class DynamicImports extends Component {

	constructor(props) {
		super(props);
		this.state = {

		}
	}

	async getComponent(){
		//使用import()语法,动态导入必须是npm包模块
		//本地本身封装的模块没法使用这种模式,属于非法操做
		let $ = await import(/* webpackMode: "jquery" */'jquery')
		let dom = document.createElement('div')
		$('dom').text('some text')
		return dom
	}

	insert(){
		this.getComponent().then(component => {
			document.getElementById('box').appendChild(component);
		});
	}

	render() {
		return (
			<div id='box'>
				<div>点击下面这个按钮,动态插入较大的DOM</div>
				<span className='btn' onClick={()=>{this.insert()}}>click</span>
			</div>
		);
	}
}

export default DynamicImports;
  • webpack特定的语法require.ensure(),能够在打包时识别须要分块的代码,这个功能独立于前端框架。
  • require.ensure() 是 webpack 特有的,已经被 import() 取代。
//路由中的组件经过getComponent方法引入
//组件引入经过require.ensure,并在webpack中配置chunkFilename
const chooseProducts = (location, cb) => {
    require.ensure([], require => {
        cb(null, require('../Component/chooseProducts').default)
    },'chooseProducts')
}

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

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

const RouteConfig = (
    <Router history={history}>
        <Route path="/" component={Roots}>
            <IndexRoute component={index} />//首页
            <Route path="index" component={index} />
            <Route path="helpCenter" getComponent={helpCenter} />//帮助中心
            <Route path="saleRecord" getComponent={saleRecord} />//销售记录
            <Redirect from='*' to='/'  />
        </Route>
    </Router>
);

二、react框架自身的代码分离方案-懒加载

click herenpm

{
  "presets": ["@babel/react"],
  "plugins": ["@babel/plugin-syntax-dynamic-import"]
}
import Loadable from "react-loadable";
import Loading from "./Loading";

const LoadableComponent = Loadable({
  loader: () => import("./Dashboard"),
  loading: Loading
});

export default class LoadableDashboard extends React.Component {
  render() {
    return <LoadableComponent />;
  }
}

参考资料

感谢阅读,欢迎评论^-^redux

打赏我吧^-^