小二助手-react.js分块加载
小二助手在线演示地址:http://118.25.217.253:8000 账号test 密码123
小二助手是用material-ui开发的,感觉国内使用的人数不是特别多,所以创建了一个qq交流群,欢迎加入:
在网上找了多种方案,这种采纳了下面的方案,简单有效。
一、为什么需要代码分片
Facebook 的 create-react-app 是一款非常优秀的开发脚手架。它为我们生成了 React 开发环境,自带 webpack 默认配置。 它会通过 webpack 打包我们的应用,产生一个 bundle.js 文件。随着我们的项目越写越复杂,bundle.js 文件会随之增大。
由于该文件是唯一的,所以不管用户查看哪个页面、使用哪个功能,都必须先下载所有的功能代码。
当 bundle.js 大到一定程度,就会明显影响用户体验。
此时,我们就需要 code splitting ,将代码分片,实现按需异步加载,从而优化应用的性能。
二、代码分片的原理
ES模块(ECMAScript modules)都是静态的。编译时就必须指明 确定的导入(import)和导出(export)。 这也是规定 import 声明必须出现在模块顶部的原因所在。
但是我们可以通过 dynamic import() 来实现动态加载的功能。 dynamic import() 是 stage 3 中的一个提案。这是一个 运算符 operator 而非函数 function 。 我们把模块的名字作为参数传入,它会返回一个 Promise ,当模块加载完成后,该 Promise 就会 fulfilled。
当你在代码中新增了一个 import() ,用它动态导入模块时, Webpack 2 会自动据此完成代码分片,不需要任何额外的手动配置。
三、以路由为中心进行代码分片
React 项目中的路由一般用 React Router,它可以将多页面的应用构建为 SPA ,即单页面应用。
此处,我们以其最新版 React Router v4 为例。
分片前
import {requireAuthentication} from './CheckToken'
import Home from '../components/Home/Home'
import Login from './LoginContainer'
import Signup from './SignupContainer'
import Profile from './ProfileContainer'
... ...
<Router>
<Switch>
<Route exact path='/' component={Home} />
<Route path='/login' component={Login} />
<Route path='/signup' component={Signup} />
<Route path='/profile' component={requireAuthentication(Profile)} />
分片后
新增 AsyncComponent,它将接受一个函数作为参数,实现异步地动态加载组件。例如:
const AsyncLogin = asyncComponent(() => import('./LoginContainer'))
至于为什么是以 () => import('./LoginContainer') 这样的箭头函数为参数,而非 './LoginContainer' 这样的字符串,和 Webpack 的进行代码分片的机制有关。
这么写看起来啰嗦,但可以让我们控制生成多少个 .chunk.js 这样的分片文件。
代码:
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
}
路由
import {requireAuthentication} from './CheckToken'
import asyncComponent from './AsyncComponent'
const AsyncHome = asyncComponent(() => import('../components/Home/Home'))
const AsyncLogin = asyncComponent(() => import('./LoginContainer'))
const AsyncSignup = asyncComponent(() => import('./SignupContainer'))
const AsyncProfile = asyncComponent(() => import('./ProfileContainer'))
... ...
<Router>
<Switch>
<Route exact path='/' component={AsyncHome} />
<Route path='/login' component={AsyncLogin} />
<Route path='/signup' component={AsyncSignup} />
<Route path='/profile' component={requireAuthentication(AsyncProfile)} />
此时再运行 npm run build,看编译的log,以及 build/static/js/ 目录下的 js 文件,会发现多出了若干文件名 .chunk.js 结尾的文件。
npm start 把项目跑起来,在 chrome 的 devTool 中,打开 Network ,查看 JS ,就可以看到异步动态按需加载分片文件的效果了。
四、以组件为中心进行代码分片
上面一小节是以路由为中心进行代码分片的思路与实现。但是 React Router 官网说得明白,React Router 是导航组件的集合。
即,路由本身并没有什么特别的,它们也是组件。
如果以组件为中心进行代码分片,会带来额外的好处:
除了路由此外,还有很多地方可以进行代码分片。广阔天地,大有作为。
同一个组件中,针对不急着显示的东西,可以延迟其加载。
... ...
这里介绍 React Loadable 。
通过它,我们可以用使用 React 高阶组件 (Higher Order Component / HOC)实现异步加载 React 组件的功能,同时处理操作失败、网络错误等等边缘情况。
注:一个高阶组件,简言之就是一个函数,它接受的参数是 React 组件,返回的结果也是 React 组件。
React Loadable 可以通过 npm 安装 react-loadable。
首先,我们用 React Loadable 来重构刚才的代码
处理边缘情况的组件
import React from 'react'
const MyLoadingComponent = ({isLoading, error}) => {
// 加载中
if (isLoading) {
return <div>Loading...</div>
}
// 加载出错
else if (error) {
return <div>Sorry, there was a problem loading the page.</div>
}
else {
return null
}
}
export default LoadingComponent
路由
import {requireAuthentication} from './CheckToken'
import Loadable from 'react-loadable'
import LoadingComponent from '../components/common/Loading'
const AsyncHome = Loadable({
loader: () => import('../components/Home/Home'),
loading: LoadingComponent
})
const AsyncSignup = Loadable({
loader: () => import('./SignupContainer'),
loading: LoadingComponent
})
const AsyncLogin = Loadable({
loader: () => import('./LoginContainer'),
loading: LoadingComponent
})
const AsyncProfile = Loadable({
loader: () => import('./ProfileContainer'),
loading: LoadingComponent
})
<Router>
<Switch>
<Route exact path='/' component={AsyncHome} />
<Route path='/login' component={AsyncLogin} />
<Route path='/signup' component={AsyncSignup} />
<Route path='/profile' component={requireAuthentication(AsyncProfile)} />
五、进一步优化
重新运行项目,发现了可以进一步改进的地方。
防止 Loading 组件闪现
在页面跳转的时候,屏幕上会短暂的闪过 LoadingComponent 组件。
我们添加该组件的初衷,是在网络差的时候,给用户一个提示:“应用运行正常,只是正在加载中,请稍等。”
显然,如果网络良好,跳转足够快,LoadingComponent 组件根本没有必要出现。
React Loadable 可以很容易地实现这个功能。
LoadingComponent 组件接收一个 pastDelay 属性,该属性仅仅在延迟超过一个规定的值后才为 true 。
默认的延迟是 200ms,我们也可以自己指定别的时长。操作如下,我们将其设置为 300ms。
const AsyncLogin = Loadable({
loader: () => import('./LoginContainer'),
loading: LoadingComponent,
delay: 300
})
LoadingComponent 组件做相应调整。同时增加一些简单的样式。
import React from 'react'
import Footer from '../Footer/Footer'
import styled from 'styled-components' const Wrap = styled.div`
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-between;
background-color: #B2EBF2;
text-align: center;
` const LoadingComponent = (props) => {
if (props.error) {
return (
<Wrap>
<div>Error!</div>
<Footer />
</Wrap>
)
} else if (props.pastDelay) {
// 300ms 之后显示
return (
<Wrap>
<div>信息请求中...</div>
<Footer />
</Wrap>
)
} else {
return null
}
} export default LoadingComponent
同一个组件中,延迟加载不急着显示的内容
例如这个组件,TopHeader 是优先显然的内容,Notification 是不一定显示的内容。我们可以推迟后者的加载。
... ...
import TopHeader from '../components/Header/TopHeader'
import Notification from './NotificationContainer' class TopHeaderContainer extends Component {
... ... return (
<div>
<TopHeader
sideButtons={tempIsAuthenticated}
logout={this.logout}
/>
<Notification />
</div> )
}
... ...
export default connect(mapStateToProps, { logout })(TopHeaderContainer)
优化后 ... ...
import TopHeader from '../components/Header/TopHeader' import Loadable from 'react-loadable'
import LoadingComponent from '../components/common/Loading' const AsyncNotification = Loadable({
loader: () => import('./NotificationContainer'),
loading: LoadingComponent,
delay: 300
})
... ...
class TopHeaderContainer extends Component {
... ... return (
<div>
<TopHeader
sideButtons={tempIsAuthenticated}
logout={this.logout}
/>
<AsyncNotification />
</div>
)
}
}
... ...
export default connect(mapStateToProps, { logout })(TopHeaderContainer)
... ...
此外, 还可以实现 预加载(如 click 按钮显示某组件,那么在 hover 事件时就预先加载之)、服务端渲染 等等。
在此就不多做介绍了。
---------------------
作者:Beijiyang999
来源:CSDN
原文:https://blog.csdn.net/beijiyang999/article/details/78591398
小二助手-react.js分块加载的更多相关文章
- react 首屏加载优化
react 首屏加载优化,原本是在入口HTML文件中加载loading动画,但是部署在测试环境上的时候一直无法显示loading的部分,也是奇怪了,我们测试环境的部署一直跟本地的都不太一样,内外网的转 ...
- React Native :加载新闻列表
代码地址如下:http://www.demodashi.com/demo/13212.html 标签与内容页联动 上一节(React Native : 自定义视图)做到了点击标签自动移动,还差跟下面的 ...
- (转载)arcgis for js - 解决加载天地图和WMTS服务,WMTS服务不显示的问题,以及wmts服务密钥。
1 arcgis加载天地图和wmts服务 arcgis for js加载天地图的例子网上有很多,这里先不写了,后期有空再贴代码,这里主要分析下WMTS服务为什么不显示,怎么解决. 条件:这里的WMTS ...
- 深入理解React:懒加载(lazy)实现原理
目录 代码分割 React的懒加载 import() 原理 React.lazy 原理 Suspense 原理 参考 1.代码分割 (1)为什么要进行代码分割? 现在前端项目基本都采用打包技术,比如 ...
- js 模版加载到前端
js 模版加载到前端 简单有效不高端 配个路由 /js/:filename , 用 readTemplate 响应请求,前端可以按模块方式直接 require 模板 'use strict' var ...
- 前端设计中关于外部js文件加载的速度优化
在一般情况下,许多人都是将<script>写在了<head>标签中,而许多浏览器都是使用单一的线程来加载js文件的,从上往下,从左往右. 若是加载过程出错,那么网页就会阻塞,就 ...
- js动态加载以及确定加载完成的代码
利用原生js动态加载js文件到页面,并在确定加载完成后调用相关function var otherJScipt = document.createElement("script") ...
- 第三课:sea.js模块加载原理
模块加载,其实就是把js分成很多个模块,便于开发和维护.因此加载很多js模块的时候,需要动态的加载,以便提高用户体验. 在介绍模块加载库之前,先介绍一个方法. 动态加载js方法: function l ...
- js资源加载优化
互联网应用或者访问量大的应用,对js的加载优化是不可少的.下面记录几种优化方法 CDN + 浏览器缓存 CDN(content delivery network)内容分发网络, 最传统的优化方式.其 ...
随机推荐
- Python学习笔记 - day2 - PyCharm的基本使用
什么是IDE 开始学习的小白同学,一看到这三个字母应该是懵逼的,那么我们一点一点来说. 既然学习Python语言我们就需要写代码,那么代码写在哪里呢? 在记事本里写 在word文档里写 在sublim ...
- andriod开发增加一个菜单
第一步: E:\01.prj\pyscrapy\Cet4\res\menu\main.xml <menu xmlns:android="http://schemas.android ...
- linux进程的休眠(等待队列)【转】
转自:http://www.cnblogs.com/noaming1900/archive/2011/01/14/1935526.html (转载) bojan 收录于2010-10-09 阅读数: ...
- 如何在Ubuntu 16.04安装的Git【转】
转自:https://www.howtoing.com/how-to-install-git-on-ubuntu-16-04/ 介绍 现代软件开发中不可或缺的工具是某种版本控制系统. 版本控制系统允许 ...
- CTP多点触摸协议【转】
转自:http://blog.chinaunix.net/uid-26403844-id-5063920.html linux kernel 2.6.30开始对多点触摸支持,最近高通要求所有CTP器件 ...
- Spark优化之一:分布式下的map操作是闭包
例如对一个JavaPairRDD<String, String>做遍历操作,常见的,我们可以通过先通过collect()操作将它转化为Map对象再进行遍历,也可以使用Spark提供的map ...
- [ Python -1 ] 简易购物车程序
练习: 1. 要求用户输入总资产,例如:2000 2. 显示商品列表,让用户根据序号选择商品,加入购物车 3. 购买,如果商品总额大于总资产,提示账户余额不足,否则,购买成功. goods = [{' ...
- 手机端 输入法出现 input框不在屏幕中间位置的问题
/** * 修改点击input输入框时的位置 */ $('.input-footer-none').on('focus',function(){ var _this=this; setTimeout( ...
- Stack的三种含义 ----超级经典 明白了 栈 的三种含义
来自:http://www.ruanyifeng.com/blog/2013/11/stack.html ----------------------------------------------- ...
- [win7] 带网络的安全模式,启动QQEIMPlatform第三方服务
REG ADD "HKLM\SYSTEM\CurrentControlSet\Control\SafeBoot\Network\QQEIMPlatform" /VE /T REG_ ...