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

参考资料

相关推荐
编程零零七1 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
(⊙o⊙)~哦3 小时前
JavaScript substring() 方法
前端
无心使然云中漫步4 小时前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者4 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_4 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
麒麟而非淇淋5 小时前
AJAX 入门 day1
前端·javascript·ajax
2401_858120535 小时前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab
阿树梢5 小时前
【Vue】VueRouter路由
前端·javascript·vue.js
随笔写7 小时前
vue使用关于speak-tss插件的详细介绍
前端·javascript·vue.js
史努比.7 小时前
redis群集三种模式:主从复制、哨兵、集群
前端·bootstrap·html