作者 DBCdouble
一、前言
本文将基于上一篇文章《Webpack4+Babel7优化70%速度》所搭建的环境去做动态路由加载,同时完成 React16 和 React-Router4 的升级工作,使整个项目的技术栈以及性能体验尽可能达到最佳状态。
二、背景
我们知道,Webpack主要从两个方面进行优化,一个是提升构建速度,另一个则是减小文件体积,而在上一篇文章《Webpack4+Babel7优化70%速度》中,我们已经完成了提升构建速度的部分,这一章我们将通过实现动态加载路由的方式来将最终生成的打包文件拆分成多个子文件来减小bundle.js的体积,这样就能极大的减小首屏加载过慢的痛点,至此之后,也就再也不用担心随着应用越来越大,bundle.js的体积越来越大导致首屏加载的速度越来越慢的问题了。
三、升级模块
这里一个个模块安装升级是为了更准确地来把控更新之后有可能引起的报错
1、React16
npm install react@16.8.4 --save
这里安装目前react的最新版本v16.8.4,安装完成之后开启项目,发现页面报错,如图:
出现上面的报错的原因是React v15.5及以上版本已经将PropTypes模块剔除,然后执行
2、react-router-dom(这里使用react-router-dom,它基于react-router,加入了在浏览器运行环境下的一些功能)
npm uninstall react-router && npm install react-router-dom --save
react-router-dom
依赖react-router
,所以我们使用npm
安装依赖的时候,只需要安装相应环境下的库即可,不用再显式安装react-router
- 将代码中的import { Link } from 'react-router'修改为import { Link } from 'react-router-dom'
- 因为react-router4.x版本较之前版本改动较大,基本需要重写项目中的路由层
四、路由配置
1、入口文件
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';import App from './app';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('app')
);
BrowserRouter是一个高阶组件,内置history的api来保持 UI 和 URL 的同步
2、路由配置
// routes.js
import Home from './home';
import About from './about';
import Help from './help';
export default [{
path: '/',
exact: true,
component: Home
}, {
path: '/about',
component: About
}, {
path: '/help',
component: Help
}];
// app.js
import React from 'react';
import { Switch, Route } from 'react-router';
import routes from './routes';
class App extends React.Component {
render() {
return (
<Switch>
{routes.map((route, i) => <Route key={i} exact={!!route.exact} path={route.path} component={route.component} />)}
</Switch>
);
}
}
export default App;
Switch用于渲染与路径匹配的第一个子 <Route>
或 <Redirect>
。
五、异步动态加载路由和Code Splitting
异步动态加载路由从狭义上理解就是页面上没有出现的页面不加载对应的js和css,只加载当前页面展示出来页面的js和css,通过动态导入(dynamic imports)文件的方式实现代码拆分
1、封装一个高阶函数来异步加载组件
//async_load.js
import React, { Component } from 'react'
export default (loadComponent, placeholder = '拼命加载中...') => {
return class AsyncComponent extends Component {
unmount = false
constructor () {
super()
this.state = {
Child: null
}
}
componentWillUnmount () {
this.unmount = true
}
async componentDidMount () {
const { default: Child } = await loadComponent()
if (this.unmount) return
this.setState({
Child
})
}
render () {
const { Child } = this.state
return (
Child ? <Child {...this.props} /> : placeholder
)
}
}
}
2、修改路由配置文件
// routes.js
import React from 'react';
import Load from './async_load';
export default [{
path: '/',
exact: true,
component(props) {
// 这里的 component 函数也是一个高阶组件
return <Load {...props} load={() => import('./home')} />;
}
}, {
path: '/about',
component(props) {
return <Load {...props} load={() => import('./about')} />;
}
}, {
path: '/help',
component(props) {
return <Load {...props} load={() => import('./help')} />;
}
}];
当涉及到动态代码拆分时,webpack 提供了两个类似的技术。对于动态导入,第一种,也是优先选择的方式是,使用符合 ECMAScript 提案 的 import()
语法。第二种,则是使用 webpack 特定的 require.ensure
。
完成以上配置之后开启打包,出现一下错误
因为import语法还处于提案阶段,所以需要通过安装Babel的插件@babel/plugin-syntax-dynamic-import才能使用,安装完成之后需要在配置babel-loader的options:
{
plugins: ['@babel/plugin-syntax-dynamic-import']
}
完成以上的步骤之后,想必已经没问题了吧,于是启动项目,又报错了:
在这个报错上我停留了太久,于是把问题抛给了一个朋友(大佬),在webpack在github上的Issue找到了答案,原来是webpack的4.29.x版本有bug
于是我回退了版本webpack@4.28.2,最终启动成功,可以看到bundle.js被拆分成多个子js文件
在浏览器打开开发者工具点击network查看请求的js文件,可以看到每进入一个新的页面,会动态加载一个新的js
进入首屏路由页面
进入另外一个路由页面
六、总结
不得不说升级老项目过程很痛苦,坑也很多。但是把坑一个个填完,最终完美升级也是一件很有意思,很有成就感的事。希望这篇文章能对你有所帮助。
文章有任何不清楚或者不准确的地方,麻烦在评论区指出