前言
随着你的应用的增长,你的包文件也会增长。特别是如果你引用了大型的第三方库。你需要密切关注包含在你包文件中的代码,这样你就不会意外地把它弄得太大,以至于你的应用程序需要很长时间才能加载完成。这是一段摘自官网的说明,详情点击官网说明
代码拆分 你的应用程序可以帮助你 “懒加载(lazy-load)” 用户当前需要的东西,这可以显着提高您的应用程序的性能。虽然你没有减少应用程序中的代码总量,但是你已经避免了加载用户可能不需要的代码,并且减少了初始加载过程中的代码量。
import()
将代码拆分引入到应用程序中的最好方法是通过动态 import() 语法。
注意:
动态 import() 语法是ECMAScript(JavaScript)提案,目前不是语言标准的一部分。预计在不远的将来会被接受。
如下面的例子:
export default class Home extends Component {
state = {
Load: null
};
showLoad = () => {
const { Load } = this.state;
if (!Load) {
return <div>Loading123</div>;
}
return <Load />;
}
componentWillMount () {
import('./load').then(load => {
this.setState({ Load: load.default });
});
}
render () {
return (
<div>
{
this.showLoad()
}
</div>
)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
四种方式
目前处理代码分割有四种比较好的处理方式:
- 1.webpack
- 2.React.lazy
- 3.Loadable Components
- 4.自定义AsyncComponent组件
那咱们一个一个的来看看是怎么处理的,let's go
在我们 react app 中,常见的路由配置可能是像下面一样的
export default () => (
<Switch>
<Route path="/" exact component={Home} />
<Route path="/Home" exact component={Home} />
<Route path="/my" exact component={My} />
<Route path="/find" exact component={Find} />
</Switch>
);
2
3
4
5
6
7
8
webpack的实现
webpack代码分割
在最开始使用Webpack的时候, 都是将所有的js文件全部打包到一个build.js文件中(文件名取决与在webpack.config.js文件中output.filename), 但是在大型项目中, build.js可能过大, 导致页面加载时间过长. 这个时候就需要code splitting, code splitting就是将文件分割成块(chunk), 我们可以定义一些分割点(split point), 根据这些分割点对文件进行分块, 并实现按需加载。
代码分割,也就是Code Splitting一般需要做这些事情:
1 为 Vendor 单独打包(Vendor 指第三方的库或者公共的基础组件,因为 Vendor 的变化比较少,单独打包利于缓存)
2 为 Manifest (Webpack 的 Runtime 代码)单独打包
3 为不同入口的公共业务代码打包(同理,也是为了缓存和加载速度) 4 为异步加载的代码打一个公共的包
Code Splitting 一般是通过配置 CommonsChunkPlugin 来完成的。一个典型的配置如下,分别为 vendor、manifest 和 vendor-async 配置了 CommonsChunkPlugin。
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks (module) {
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'app',
async: 'vendor-async',
children: true,
minChunks: 3
}),
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
CommonsChunkPlugin 的特点就是配置比较难懂,大家的配置往往是复制过来的,这些代码基本上成了模板代码(boilerplate)。如果 Code Splitting 的要求简单倒好,如果有比较特殊的要求,比如把不同入口的 vendor 打不同的包,那就很难配置了。总的来说配置 Code Splitting 是一个比较痛苦的事情。
而 Long-term caching 策略是这样的:给静态文件一个很长的缓存过期时间,比如一年。然后在给文件名里加上一个 hash,每次构建时,当文件内容改变时,文件名中的 hash 也会改变。浏览器在根据文件名作为文件的标识,所以当 hash 改变时,浏览器就会重新加载这个文件。
Webpack 的 Output 选项中可以配置文件名的 hash,比如这样:
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
2
3
4
5
正如上面说的,CommonsChunkPlugin相对比较难懂,所以在webpack4废弃了 CommonsChunkPlugin,引入了 optimization.splitChunks 这个选项。
optimization.splitChunks 默认是不用设置的。如果 mode 是 production,那 Webpack 4 就会开启 Code Splitting。
默认 Webpack 4 只会对按需加载的代码做分割。如果我们需要配置初始加载的代码也加入到代码分割中,可以设置 splitChunks.chunks 为 'all'。
React.lazy的实现
react 16.6 发布了新的功能 lazy ,和一个组件 Suspense
还是上面的例子;进行改装一下
const AsyncHome = lazy(() => import('../views/Home'));
const AsyncMy = lazy(() => import('../views/My'));
const AsyncFind = lazy(() => import('../views/Find'));
export default () => (
<Switch>
<Suspense fallback={<div>Loading...</div>}>
<Route path="/" exact component={AsyncHome} />
<Route path="/Home" exact component={AsyncHome} />
<Route path="/my" exact component={AsyncMy} />
<Route path="/find" exact component={AsyncFind} />
</Suspense>
</Switch>
);
2
3
4
5
6
7
8
9
10
11
12
13
14
这种用法同样可以使用在小组件上;
const InputSeach = lazy(() => import('./InputSeach'));
export default class Home extends Component {
fallback = () => {
return (
<div>Loading...</div>
);
}
render () {
return (
<div>
<Suspense fallback={this.fallback()}>
thomas small train
<InputSeach />
</Suspense>
</div>
)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
通过操作来看,其实api来说很简单,就两句代码,这就实现了懒加载;
import React, { lazy, Suspense } from 'react';
const Child = lazy(() => import('./Child'))
但是还是需要注意的是,lazy非要搭配Suspense使用,
可以试想一下,既然是懒加载,那么当Child还未加载完成之前,这个视图怎么办?
bingo!Suspense就是为了处理这个的,让视图更友好,为懒加载组件做优雅降级,它叫加载指示器
Suspense 可以放在懒加载的组件外层的任意位置,但是必须要添加fallback属性,不然会报错,fallback是懒加载组件载入过程中的一个过渡,可以放一些过渡效果或方法。或者放一个loading的组件提示;
注意:React.lazy和 Suspense 尚不可用于服务器端,如果做服务端渲染的同学官方还是建议使用 React Loadable 或者下面这家伙
Loadable Components的实现
这个种方式比较简单,就是安装一个包即可,里面已经做了相关个配置;详情请看官网;他的文档做的非常的详细,这里,我们就引用官网的例子即可:
npm install @loadable/component
const OtherComponent = loadable(() => import('./OtherComponent'))
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
)
}
2
3
4
5
6
7
8
9
自定义AsyncComponent组件的实现
回到开始的那个路由引入的例子,我们一开始引入这些组件,然后定义好的路径,会根据我们的路由去匹配这些组件。
但是,我们静态地在顶部导入路由中的所有组件。这意味着,不管哪个路由匹配,所有这些组件都被加载。我们只想加载对匹配路由的时候才加载响应的组件。下面我们一步步来完成这个使命。
创建一个js 文件,如:src/components/AsyncComponent.js,代码如下:
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
我们在这里做了一些事情:
- 这个asyncComponent 函数接受一个importComponent 的参数,importComponent 调用时候将动态引入给定的组件。
- 在componentDidMount 我们只是简单地调用importComponent 函数,并将动态加载的组件保存在状态中。
- 最后,如果完成渲染,我们有条件地提供组件。在这里我们如果不写null的话,也可提供一个菊花图,代表着组件正在渲染。
现在让我们使用我们的异步组件,而不是像开始的静态去引入。
import Home from '../views/Home';(这是静态引入)
我们要用asyncComponent组件来动态引入我们需要的组件。
import asyncComponent from '../components/AsyncComponent
const AsyncHome = asyncComponent(() => import('../views/Home'));
我们将要使用 AsyncHome 这个组件在我们的路由里面
< Route path="/" exact component={AsyncHome} />
修改一开始的例子:
const AsyncHome = AsyncComponent(() => import('../views/Home'))
const AsyncMy = AsyncComponent(() => import('../views/My'))
const AsyncFind = AsyncComponent(() => import('../views/Find'))
export default () => (
<Switch>
<Route path="/" exact component={AsyncHome} />
<Route path="/Home" exact component={AsyncHome} />
<Route path="/my" exact component={AsyncMy} />
<Route path="/find" exact component={AsyncFind} />
</Switch>
);
2
3
4
5
6
7
8
9
10
11
12
现在你运行npm run build 您将看到代码已经被分割成一个个小文件。
关于自定义组件,更多请看原文
关于react的代码分割部分,就先到此。
感谢蚂蚁金服数据体验技术;