文章目录
  1. 1. 背景
  2. 2. 方案
    1. 2.1. react-loadable
    2. 2.2. React.lazy()
  3. 3. React.lazy使用
    1. 3.1. Suspense
    2. 3.2. 异常捕获Error boundaries
  4. 4. 代码分割
    1. 4.1. 基于组件的代码分割
    2. 4.2. 基于路由的代码分割
  5. 5. 命名导出
  6. 6. React.memo()
    1. 6.1. React.PureComponent
    2. 6.2. React.memo()
    3. 6.3. 深层对比方案

React 16.6.0添加了React.lazy()和React.memo()组件

背景

React 打包时,如果不进行特殊处理,则会将多个文件打包合并为一个bundle文件,然后引入bundle文件,则整个应用可以一次性加载。

随着应用的增长,bundle文件也随之增长,尤其是在整合了许多第三方库的情况下,单文件体积过大,导致加载时间过长。

方案

react-loadable

在React 16.6.0之前,解决方案是引入第三方库react-loadable,将代码分批打包,使单文件体积变小,而且可以“按需加载”。

但是这样的话,写起来比较麻烦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyComponent extends React.Component {
state = {
AnotherComponent: null
};
componentWillMount() {
import('./another-component').then(AnotherComponent => {
this.setState({ AnotherComponent });
});
}
render() {
let {AnotherComponent} = this.state;
if (!AnotherComponent) {
return <div>Loading...</div>;
} else {
return <AnotherComponent/>;
};
}
}

React.lazy()

因此,在React 16.6.0版本,官方添加了React.lazy()组件,使其调用更方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const OtherComponent = React.lazy(() => import('./OtherComponent'));
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { }
}
render() {
return (
<div>
<OtherComponent />
</div>
)
};
}

React.lazy使用

React.lazy 接受一个函数,这个函数需要动态调用import()。它必须返回一个Promise,该 Promise 需要 resolve 一个 defalut export 的 React 组件。

当然了,在调用lazy之前,我们还需要了解一个组件。

Suspense

Suspense组件可以在应用的组件没有被加载完成时,给用户提示。

该组件可以接受一个fallback函数,作为提示语。

1
2
3
4
5
6
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>

异常捕获Error boundaries

如果因为一些原因(如:网络原因),导致模块加载失败。则触发异常捕获边界(Error boundaries)来处理异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import MyErrorBoundary from './MyErrorBoundary';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { }
}
render() {
return (
<div>
<MyErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</MyErrorBoundary>
</div>
);
}
}

代码分割

基于组件的代码分割

分割组件,打开控制台的Network,可以发现有一个bundle.js 和 分批打包后的4个chunk.js。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React, { Component, lazy, Suspense } from 'react';
const Header = lazy(() => import('./component/header'));
const Body = lazy(() => import('./component/body'));
const Footer = lazy(() => import('./component/footer'));
class App extends Component {
constructor(props) {
super(props);
this.state = { }
}
render() {
return (
<>
<Suspense fallback="loading...">
<Header />
<Body />
<Footer />
</Suspense>
</>
);
}
}
export default App;

基于路由的代码分割

分割路由,打开控制台的Network,可以发现每点击a标签按钮,都会请求分批打包后对应的分割后的代码。

1
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
31
32
33
34
35
36
37
38
import React, { Component, lazy, Suspense } from 'react';
import { HashRouter as Router, Route } from 'react-router-dom';
import './App.css';
// import Home from './component/home';
// import News from './component/news';
// import Share from './component/share';
// import About from './component/about';
const Home = lazy(() => import('./component/home'));
const News = lazy(() => import('./component/news'));
const Share = lazy(() => import('./component/share'));
const About = lazy(() => import('./component/about'));
class App extends Component {
render() {
return (
<>
<section>
<a href="/#/home">home</a>
<a href="/#/news">news</a>
<a href="/#/share">share</a>
<a href="/#/about">about</a>
</section>
<Suspense fallback="loading...">
<Router>
<Route path="/" exact component={Home}></Route>
<Route path="/home" component={Home}></Route>
<Route path="/news" component={News}></Route>
<Route path="/share" component={Share}></Route>
<Route path="/about" component={About}></Route>
</Router>
</Suspense>
</>
);
}
}
export default App;

命名导出

React.lazy 目前只支持默认导出的组件

1
export default MyDefaultComponent

如果想支持使用命名导出的组件模块,则需要创建一个中间模块,封装一下,来进行重新导出。

1
2
3
4
5
6
7
8
9
// ManyComponent.js
export const ComponentA = /*....*/;
export const ComponentB = /*....*/;
export const ComponentIWanted = /*....*/;
// MyComponent.js
export { ComponentIWanted as default } from './ManyComponent.js';
// MyApp.js
import React, { lazy } from 'react';
const MyComponent = lazy(() => import("./MyComponent.js"));

React.memo()

在说React.memo()之前,先说一下React.PureComponent。

React.PureComponent

React.PureComponent 与 React.Component类似,都是用来定义React组件的基类。区别在于React.Component并未实现shouldComponentUpdate(),而React.PureComponent对组件的state和prop实现了浅层对比,所以使用React.PureComponent可以提高性能。

React.PureComponent仅作对象的浅层对比,如果数据结构比较复杂,则可能产生错误的对比结果

React.memo()

React.memo是高阶组件,它和React.PureComponent作用类似,区别在于:React.PureComponent适用于class组件,而React.memo适用于函数组件(函数组件后面的更新会讲到)。

1
2
3
const MyComponent = React.memo(function MyComponent(props) {
/* 使用 props 渲染 */
});

如果你的函数组件在给定相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

深层对比方案

刚才说了,React.memo()仅作为浅层对比,如果有部分深层次的复杂对象,则需要手动控制对比过程。

1
2
3
4
5
6
7
8
9
10
11
function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);

此方法仅作为性能优化的方式而存在。但请不要依赖它来“阻止”渲染,因为这会产生 bug。

本章完!

文章目录
  1. 1. 背景
  2. 2. 方案
    1. 2.1. react-loadable
    2. 2.2. React.lazy()
  3. 3. React.lazy使用
    1. 3.1. Suspense
    2. 3.2. 异常捕获Error boundaries
  4. 4. 代码分割
    1. 4.1. 基于组件的代码分割
    2. 4.2. 基于路由的代码分割
  5. 5. 命名导出
  6. 6. React.memo()
    1. 6.1. React.PureComponent
    2. 6.2. React.memo()
    3. 6.3. 深层对比方案