1. 背景
在React应用中,当我们使用这样的语句导入某个模块(例如:一个组件)的时候,就称之为静态导入
TypeScript
import sth from 'module1'
凡是被静态导入的模块,都会被打包到初始的bundle里面
如果一个应用内所有模块都使用静态导入,会导致
- 初始bundle的体积较大
- 浏览器加载bundle的速度较慢
因此:针对不需要被包含在首次渲染的模块,可以考虑使用动态导入,这样可以减少初始bundle的体积,从而提升首次渲染的性能
以组件为例,符合以下特征的组件其实都不需要被包含在首次渲染中
- 依赖用户交互行为的 -> Import on interaction
- 首屏不可见的 -> Import in viewport
- 不属于当前路由的 -> Route based splitting
2. Import on interaction
发生交互行为后(例如点击按钮),再加载组件
2.1 React Lazy
使用React提供的lazy导入组件
- 使用lazy函数导入组件
- 使用Suspense对组件进行包裹,并设置fallback来展示组件加载中的状态
TypeScript
import { Suspense, lazy, useState } from 'react'
import ComponentA from './ComponentA'
import ComponentB from './ComponentB'
const ComponentB = lazy(() => import('./ComponentB'))
export default function () => {
const [visible, setVisible] = useState(false)
function toggeleVisible() {
setVisible(v => !v)
}
return (
<Suspense fallback={<p>loading...</p>}>
<ComponentA />
<button onClick={toggleVisible}>click me to toggle ComponentC</button>
{
visible && <ComponentC />
}
</Suspense>
)
}
2.2 import()
EsModule提供的import函数返回的是一个promise,值为module
通过import函数拿到组件的引用后,可以通过createElement的方式创建一个jsx元素进行渲染
TypeScript
import { createElement, useState } from 'react'
import ComponentA from './ComponentA'
export default function () => {
const [componentC, setComponentC] = useState(null)
function showComponentC() {
import('./ComponentC')
.then(module => module.default)
.then(component => {
setComponentC(createElement(component))
})
}
function hideComponentC() {
setComponentC(null)
}
return (
<>
<ComponentA />
<button onClick={() => {
if (componentC) {
hideComponentC()
} else {
showComponentC()
}
}}>
click me to toggle ComponentC
</button>
{componentC}
</>
)
}
需要注意的是,使用Babel的时候,需要用@babel/plugin-syntax-dynamic-import插件确保Babel能够进行正确的转译
3. Import in viewport
当组件对应的UI进入视窗内的时候,再加载组件
下面提到的这两个库都是基于IntersectionObserver这个API实现的
3.1 react-loadable-visibility
TypeScript
import LoadableVisibility from "react-loadable-visibility/react-loadable"
import ComponentA from './ComponentA'
import ComponentB from './ComponentB'
const ComponentC = LoadableVisibility({
loader: () => import('./ComponentC')
loading: <p>Loading...</p>
})
const App = () => {
return (
<>
<ComponentA />
<ComponentB />
<ComponentC />
</>
)
}
export default App
3.2 react-lazyload
TypeScript
import LazyLoad from 'react-lazyload'
import ComponentA from './ComponentA'
import ComponentB from './ComponentB'
const App = () => {
return (
<>
<ComponentA />
<ComponentB />
<LazyLoad height='300px'>
<ComponentC />
</LazyLoad>
</>
)
}
export default App
相比于react-loadable-visibility,react-lazyload的功能更加强大,因为它提供了更多的API,让开发者能够通过这些API进行灵活的自定义配置,例如:
-
offset:如果需要提前加载组件,例如在视窗下边缘距离组件100px时就进行加载,可以设置offset为100
-
placeholder:组件未加载时的占位符
4. Route based splitting
基于路由来分割bundle,每个路由对应的根组件单独生成一个bundle
TypeScript
import { RouteConfig } from 'react-router-config'
import { lazy } from 'react'
export const routes: RouteConfig[] = [
{
path: '/list',
component: lazy(() => import('./src/List'))
},
{
path: '/detail',
component: lazy(() => import('./src/Detail'))
}
]