react如何实现代码分割,路由动态加载

众所周知,在使用webpack打包react应用时,webpack将整个应用打包成一个js文件,当用户访问首屏时,会一次性加载整个js文件,当应用的规模变得越来越庞大的时候,首屏渲染速度变慢,影响用户体验。

于是,webpack开发了代码分割的特性, 此特性能够把代码分割为不同的bundle文件,然后可以通过路由按需加载或并行加载这些文件。

代码分割可以用于获取更小的bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。有三种常用的代码分割方法:

1、拆分入口:使用 entry 配置手动地分割代码。

2、防止重复:使用 CommonsChunkPlugin 去重和分离chunk。

3、动态导入:通过模块的内联函数调用来分离代码。本文只讨论动态导入(dynamic imports)的方法。

动态导入

当涉及到动态代码拆分时,webpack提供了两个类似的技术。对于动态导入,第一种,也是优先选择的方式是,使用符合ECMA提案的 import() 语法。第二种,则是使用 webpack 特定的 require.ensure。本文使用第一种方式。

注意:import() 调用会在内部用到promise。如果在旧有版本浏览器中使用 import(),记得使用一个polyfill 库(例如 es6-promise 或 promise-polyfill),来 shim Promise。

下面结合react-router 4来实现react的代码分割。

在React应用中实现

React应用的代码分割需要结合路由库react-router使用,当前react-router的版本是V4,在使用react-router4进行代码分割的路上,社区已经有成熟的第三方库进行了实现,如react-loadable。在此处将介绍如何不借助第三方库实现代码分割。

此处假设你已经对react、react-router4、webpack有基本的了解,可以搭建简单的开发环境。下面是本项目的基本目录结构:

项目入口文件src/index.js:

 

src/App.js:

在App.js中,引入react-router-dom路由模块,以及路由配置文件routes.js,App组件主要负责通过路由配置遍历生成一系列路由组件。

下面是路由配置src/routes.js:

routes.js中配置了路由组件需要的参数,需要注意的是在路由参数中使用了异步组件AsyncComponent,注意这里并没有直接引入组件,而是传递一个函数参数给AsyncComponent,它将在AsyncComponent(() => import('./containers/home'))组件被创建时进行动态引入。

同时,这种传入一个函数作为参数,而非直接传入一个字符串的写法能够让webpack意识到此处需要进行代码分割。

使用import()需要使用Babel预处理器和动态import的语法插件(Syntax Dynamic Import Babel Plugin)。由于 import() 会返回一个 promise,因此它可以和ES7的async函数一起使用,使用acync函数需要安装babel-plugin-transform-runtime插件。

安装babel插件:

本项目使用的其他babel插件还有babel-core、babel-loader、babel-preset-env、babel-preset-react等,主要用于React的jsx语法编译。

下面需要编写babel配置文件.babelrc,在根目录下新建.babelrc,配置如下:

异步组件AsyncComponent

代码分割的核心部分就是实现AsyncComponent,本项目的AsyncComponent放在src/components/async-component/index.js中,代码如下:

整个模块是一个高阶组件,返回一个新的组件,传入两个参数,一个是需要动态加载组件的方法,第二个是动态加载时的占位符,占位符的默认参数为一个字符串,也可以传入一个Loading组件。

在返回的AsyncComponent组件内部,constructor中,初始化一个state为Child,值为null,并定义this.unmount =false,用于表示组件是否被卸载。

使用acync定义异步方法,componentDidMount中,使用await异步执行传入的第一个参数,用于动态加载当前路由的组件。

注意:

当调用ES6模块的import()方法(引入模块)时,必须指向模块的.default值,因为它才是promise 被处理后返回的实际的module对象。

故此处使用ES6的对象解构获取到模块的default并赋值到Child上。

然后判断组件被卸载的状态,被卸载即返回。

下面将Child设置到state上。

在render方法中,从state中获取Child,然后使用三元运算符判断Child是否存在,存在则渲染Child组件,并传入this.props,否则渲染占位符。

组件componentWillUnmount时,设置this.unmout为true。

测试

现在开始编写一些简单的业务组件用于测试,在containers中新建两个文件夹home和detail,在两个文件夹下编写index.js作为两个路由组件。代码如下:

containers/home/index.js:

containers/detail/index.js:

在根目录下package.json配置启动脚本:

然后运行npm start启动项目:

打开浏览器访问localhost:8080

查看右侧network面板,可以看到页面先加载了main.js和0.js,点击详情按钮跳转到http://localhost:8080/detail

随后加载了1.js,这样就实现了代码分割,每个路由都是动态加载的。在大型React应用中,将bundle进行细粒度的拆分,可以极大提升首屏渲染速度,提升用户体验。

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值