React router动态加载组件-适配器模式的应用
前言
本文讲述怎么实现动态加载组件,并借此阐述适配器模式。
一、普通路由例子
import Center from 'page/center';
import Data from 'page/data';
function App(){
return (
<Router>
<Switch>
<Route exact path="/" render={() => (<Redirect to="/center" />)} />
<Route path="/data" component={Data} />
<Route path="/center" component={Center} />
<Route render={() => <h1 style={{ textAlign: 'center', marginTop: '160px', color:'rgba(255, 255, 255, 0.7)' }}>页面不见了</h1>} />
</Switch>
</Router>
);
}
复制代码
以上是最常见的React router
。在简单的单页应用中,这样写是ok的。因为打包后的单一js文件bundle.js
也不过200k左右,gzip
之后,对加载性能并没有太大的影响。
但是,当产品经历多次迭代后,追加的页面导致bundle.js
的体积不断变大。这时候,优化就变得很有必要。
二、如何优化
优化使用到的一个重要理念就是——按需加载。
可以结合例子进行理解为:只加载当前页面需要用到的组件。
比如当前访问的是/center
页,那么只需要加载Center
组件即可。不需要加载Data
组件。
业界目前实现的方案有以下几种:
- react-router的动态路由
getComponent
方法(router4已不支持) - 使用react-loadable小工具库
- 自定义高阶组件进行按需加载
而这些方案共通的点,就是利用webpack的code splitting
功能(webpack1使用require.ensure
,webpack2/webpack3使用import
),将代码进行分割。
接下来,将介绍如何用自定义高阶组件实现按需加载。
三、自定义高阶组件
3.1 webpack的import方法
webpack将
import()
看做一个分割点并将其请求的module打包为一个独立的chunk。import()
以模块名称作为参数名并且返回一个Promise对象。
因为import()
返回的是Promise对象,所以不能直接给<Router/>
使用。
3.2 采用适配器模式封装import()
适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
当前场景,需要解决的是,使用import()
异步加载组件后,如何将加载的组件交给React进行更新。
方法也很容易,就是利用state
。当异步加载好组件后,调用setState
方法,就可以通知到。
那么,依照这个思路,新建个高阶组件,运用适配器模式
,来对import()
进行封装。
3.3 实现异步加载方法asyncComponent
import React from 'react';
export const asyncComponent = loadComponent => (
class AsyncComponent extends React.Component {
constructor(...args){
super(...args);
this.state = {
Component: null,
};
this.hasLoadedComponent = this.hasLoadedComponent.bind(this);
}
componentWillMount() {
if(this.hasLoadedComponent()){
return;
}
loadComponent()
.then(module => module.default ? module.default : module)
.then(Component => {
this.setState({
Component
});
})
.catch(error => {
/*eslint-disable*/
console.error('cannot load Component in <AsyncComponent>');
/*eslint-enable*/
throw error;
})
}
hasLoadedComponent() {
return this.state.Component !== null;
}
render(){
const {
Component
} = this.state;
return (Component) ? <Component {...this.props} /> : null;
}
}
);
复制代码
// 使用方式
const Center = asyncComponent(()=>import(/* webpackChunkName: 'pageCenter' */'page/center'));
复制代码
如例子所示,新建一个asyncComponent
方法,用于接收import()
返回的Promise对象。
当componentWillMount
时(服务端渲染也有该生命周期方法),执行import()
,并将异步加载的组件,set
入state
,触发组件重新渲染。
3.4 释疑
- state.Component初始化
this.state = {
Component: null,
};
复制代码
这里的null
,主要用于判断异步组件是否已经加载。
module.default ? module.default : module
这里是为了兼容具名
和default
两种export
写法。
return (Component) ? <Component {...this.props} /> : null;
这里的null
,其实可以用<LoadingComponent />
代替。作用是:当异步组件还没加载好时,起到占位的作用。this.props
是通过AsyncComponent
组件透传给异步组件的。
3.5 修改webpack构建
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
}
复制代码
在输出项中,增加chunkFilename
即可。
四、小结
自定义高阶组件的好处,是可以按最少的改动,来优化已有的旧项目。
像上面的例子,只需要改变import
组件的方式即可。花最少的代价,就可以得到页面性能的提升。
其实,react-loadable
也是按这种思路去实现的,只不过增加了很多附属的功能点而已。
参考
作者:我是leon
链接:https://juejin.im/post/5b9850cae51d450e74288576
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关文章
- [ExtJS5学习笔记]第八节 Extjs5的Ext.toolbar.Toolbar工具条组件及其应用
- 【Vue】Vue组件中style中的局部样式设置scoped应用
- 【Vue】Vue组件中事件和方法的混合(mixin)应用(图文+代码示例)
- 有序表的应用:[Leetcode] 480. 滑动窗口中位数
- JavaEE Web应用开发平台WebBuilder开发团队专访
- spring boot 应用发布到Harbor上
- Web应用扫描工具Wapiti
- python中stack在实际中的简单应用之平衡符号
- 【Analytics Kit】【FAQ】集成华为分析服务问题集合 SDK初始化失败/应用调试界面没有数据/常见错误提示
- 如何在快应用中实现滑动操作组件
- 《iOS组件与框架——iOS SDK高级特性剖析》——第2章,第2.1节示例应用
- SwiftUI iOS 开源组件之 01 超酷步骤指示Environment经典应用(教程含源码)
- 《数据库技术原理与应用教程(第2版)》——第3章 数据管理中的数据模型 3.1 数据模型的基本概念
- 5分钟,全面了解政府大数据门类及应用!
- (Relax 数论1.8)POJ 1284 Primitive Roots(欧拉函数的应用: 以n为模的本原根的个数phi(n-1))
- favicon.ico网页图标的制作与应用
- 【快应用】switch组件开关的动态控制
- 【快应用】如何限制input组件的输入值个数
- 【快应用】销毁自定义组件,避免组件事件被多次触发
- 【快应用】input组件的输入框弹出后,如何点击其他地方后失去焦点,并收起键盘
- 【快应用】实现自定义导航栏组件
- 现阶段的物联网应用噱头大于必要性