React组件实现动态导入的几种方案

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

使用 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

使用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'))
  }
]

参考资料

相关推荐
沉默璇年6 分钟前
react中useMemo的使用场景
前端·react.js·前端框架
yqcoder12 分钟前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
2401_8827275722 分钟前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架
会发光的猪。1 小时前
css使用弹性盒,让每个子元素平均等分父元素的4/1大小
前端·javascript·vue.js
天下代码客1 小时前
【vue】vue中.sync修饰符如何使用--详细代码对比
前端·javascript·vue.js
猫爪笔记1 小时前
前端:HTML (学习笔记)【1】
前端·笔记·学习·html
前端李易安2 小时前
Webpack 热更新(HMR)详解:原理与实现
前端·webpack·node.js
红绿鲤鱼2 小时前
React-自定义Hook与逻辑共享
前端·react.js·前端框架
Domain-zhuo2 小时前
什么是JavaScript原型链?
开发语言·前端·javascript·jvm·ecmascript·原型模式
小丁爱养花2 小时前
前端三剑客(三):JavaScript
开发语言·前端·javascript