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

参考资料

相关推荐
倚栏听风雨31 分钟前
npm命令详解
前端
用户479492835691541 分钟前
为什么我的react项目启动后,dom上的类名里没有代码位置信息
前端·react.js
键盘飞行员1 小时前
Vue3+TypeScript项目中配置自动导入功能,遇到了问题需要详细的配置教程!
前端·typescript·vue
han_1 小时前
前端高频面试题之Vue(初、中级篇)
前端·vue.js·面试
一枚前端小能手1 小时前
📜 `<script>`脚本元素 - 从加载策略到安全性与性能的完整指南
前端·javascript
掘金安东尼1 小时前
TypeScript为何在AI时代登顶:Anders Hejlsberg 的十二年演化论
前端·javascript·面试
yong99901 小时前
MATLAB倍频转换效率分析与最佳匹配角模拟
开发语言·前端·matlab
面向星辰1 小时前
扣子开始节点和结束节点
java·服务器·前端
执携2 小时前
Vue Router (命名视图)
前端·javascript·vue.js
John_Rey2 小时前
Rust类型系统奇技淫巧:幽灵类型(PhantomData)——理解编译器与类型安全
前端·安全·rust