React 异步动态加载组件

发布于 2022-11-29 12:45:59 字数 5210 浏览 185 评论 0

本文讲述怎么实现动态加载组件,并借此阐述适配器模式。

一、普通路由例子

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 方法(router 4 已不支持)
  • 使用 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 =&gt; module.default ? module.default : module)
.then(Component =&gt; {
this.setState({
Component
});
})
.catch(error =&gt; {
/*eslint-disable*/
console.error('cannot load Component in &lt;AsyncComponent&gt;');
/*eslint-enable*/
throw error;
})
}
hasLoadedComponent() {
return this.state.Component !== null;
}
render(){
const {
Component
} = this.state;
return (Component) ? &lt;Component {...this.props} /&gt; : 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 也是按这种思路去实现的,只不过增加了很多附属的功能点而已。

参考

  1. 基于 webpack Code Splitting 实现 react 组件的按需加载
  2. react 中使用 webpack2 的 import() 异步加载组件的实现

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

失与倦"

暂无简介

0 文章
0 评论
24 人气
更多

推荐作者

懂王

文章 0 评论 0

清秋悲枫

文章 0 评论 0

niceone-tech

文章 0 评论 0

小伙你站住

文章 0 评论 0

刘涛

文章 0 评论 0

南街九尾狐

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文