React中Suspense及lazy()懒加载及代码分割原理和使用的理解

React中Suspense及lazy()懒加载原理和使用的理解

Suspense和lazy()都是react中比较新的特性,在项目中使用还比较少,但是学习一下有助于在后面的项目中使用,同样可以一窥React未来的发展方向


React.lazy()

概括

顾名思义lazy()方法是用来对项目代码进行分割,懒加载用的.只有当组件被加载,内部的资源才会导入

为什么需要懒加载

在React的项目中import导入其他组件和库都是默认在初始直接导入的,webpack等打包工具会将import导入的文件直接合并到一个大文件中,如果项目很大,打包完后初始化加载时需要加载的文件会很大,这时候就需要代码分割

官方文档中的例子

项目中:

// app.js
import { add } from './math.js';

console.log(add(16, 26)); // 42
// math.js
export function add(a, b) {
  return a + b;
}

打包后

function add(a, b) {
  return a + b;
}

console.log(add(16, 26)); // 42

如何进行代码分割

在你的应用中引入代码分割的最佳方式是通过动态 import() 语法,这是官方文档中说的

动态import例子:

静态导入:

import { add } from './math';

console.log(add(16, 26));

动态导入:

import("./math").then(math => {
  console.log(math.add(16, 26));
});

使用了动态导入之后,webpack检测到这种语法会自动代码分割,也就是压缩到两个文件里

React.lazy()就是对这个种动态导入方式的优化方法

使用:

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    // Displays <Spinner> until OtherComponent loads
    <React.Suspense fallback={<Spinner />}>
      <div>
        <OtherComponent />
      </div>
    </React.Suspense>
  );
}

发现没有原来的动态导入写法像 Promise的写法链式调用,现在动态导入的方式,有点类似于Async写出来是同步使用的,可以直接讲返回的Promise对象作为组件使用,当这个组件pending是显示的是Suspensefallback的内容,只有resolve才显示加载好的组件.

所以可以看出SuspenseReact.lazy()需要结合在一起用,否则会报错缺少Placeholder UI`

Suspense

Suspense的英文意思是悬念,悬停**,Suspense的作用就是在遇到异步请求或者异步导入组件的时候等待请求和导入完成再进行渲染,**相比普通的组件, 我们是要求render是一个纯函数,一旦开始渲染不会等待,而有了Suspense后我们可以再render过程中写一部代码.

Suspense应用场景

Subspense存在两种应用场景,第一种就是动态导入组件(如上),一种就是异步请求数据(暂时不支持,大概16.9版本)

动态导入组件

动态导入组件上面的例子就是上面的代码片段,就是当这个组件pending是显示的是Suspensefallback的内容,只有resolve才显示加载好的组件,所以fallback不能为空

优点

好处在于我们可以不用创建一些组件状态的变量来控制是否显示loading画面和真正组件,这部分逻辑直接由Suspense内部实现. 而且还有一种场景就是当一个父组件中,有多个动态加载的组件,Suspense可以直接将loading状态聚合,只有当所有组件加载完成才显示,这里免去了复杂的逻辑判断.


数据请求后渲染页面

Suspense暂时还不支持数据请求后渲染, 预计是在16.9版本也就是2019年年中发布,但是以及有了实例使用的方法,下面这里就是官方的示例代码,这里做一些翻译解释

// unstable_createResource这个就是一个封装的请求数据的插件,不用太清楚类似于fetch
import {unstable_createResource} from 'react-cache';

// 声明请求数据的方法
const TodoResource = unstable_createResource(fetchTodo);

//内部请求TodoResource.read(props.id) 就是异步请求数据
function Todo(props) {
  // Suspends until the data is in the cache
  const todo = TodoResource.read(props.id);
  return <li>{todo.title}</li>;
}

function App() {
  return (
    // 只有当两个Todo内部的异步请求都完成后才能渲染出来,否则渲染<Spinner/>
    <React.Suspense fallback={<Spinner />}>
      <ul>
        {/* Siblings fetch in parallel */}
        <Todo id="1" />
        <Todo id="2" />
      </ul>
    </React.Suspense>
  );
}

这就是Suspense请求数据时的使用的方法,同样解决了loading状态的问题,相当于再render过程中加入了异步副作用操作,再普通的组件中异步操作是不起作用的,因为先渲染完,异步数据才会返回,这时候已经渲染完了.

Suspense实现原理

Subspense的实现主要就是利用了**ComponentDidCatch这个生命周期,这个什么周期就是用来捕获子组件树中的任何Javascript异常**

源码就不分析了,这里说一下大概的流程步骤:

  • 父组件渲染到子组件时发现异步请求,直接抛出错误,捕获的结果是一个Promise对象
  • ComponentDidCatch捕获这个Promise对象,pending状态下渲染fallback
  • 当resolve时重新render,遇到下一个异步请求重复上面操作
  • 直到整个父组件的抛出的promise对象都为resolve,将loading替换为真正的组件.

总结

Suspense其实就是将原来放在外面处理的异步请求也就是副作用放到渲染过程中进行操作,这样render这个函数就不再是纯函数了,但是非常直观方便,不需要再用很多状态来控制loading显示,而异步请求的结果无法预测会导致很多bug.等到Suspense支持数据请求的场景时,我会考虑把它运用到自己的项目中, 感觉确实方便很多本来就觉得Loading状态有点多余,虽然现在用的是Dva,会自动给effect创建loading状态的,还不是特别需要.

<think>好的,我现在需要帮助用户解决关于React Suspense使用方法常见问题。首先,我要回顾用户提供的引用内容,看看有哪些相关信息可以利用。用户提到了引用[1]、[3]、[4][5],其中涉及到React.lazySuspense代码拆分、懒加载以及并发模式中的useTransition。这些应该是回答的关键点。 用户之前的问题是关于Ant Design中的legacyLogicalPropertiesTransformer,现在转向React Suspense,这可能意味着他们在开发React应用时遇到了性能优化或代码拆分的问题。我需要确保回答结构清晰,分步骤解释使用方法,并解决常见问题。 首先,我应该从基础开始,介绍React Suspense的核心作用,比如代码拆分异步数据加载。结合引用[1][3],React.lazySuspense一起使用可以实现组件的延迟加载。需要详细说明如何用React.lazy动态导入组件,并用Suspense包裹,提供fallback UI。 接下来,用户可能需要知道如何与路由结合使用,比如React Router v6。引用[5]提到了useRoutes钩子,这里可以举例说明如何在路由配置中使用懒加载组件,确保代码拆分包正确加载。 然后,用户可能关心数据获取时的Suspense应用。引用[4]提到了并发模式useTransition,这里需要解释如何用Suspense处理数据加载的pending状态,结合useTransition实现平滑过渡,避免界面闪烁。 常见问题部分,需要涵盖加载失败处理、预加载策略、SSR支持以及重复渲染问题。比如,使用ErrorBoundary捕获加载错误,动态导入的预加载方法,以及SSR中React.lazy的限制,推荐使用loadable-components。 还要注意用户可能的进阶需求,比如并发模式下的特性,结合useTransitionuseDeferredValue优化用户体验,如引用[4]提到的实验性版本配置。 最后,确保引用正确,所有提到的点都有对应的引用标记,比如使用React.lazySuspense的基本方法引用[1][3],路由集成引用[5],数据加载引用[4]。 需要检查是否有遗漏点,比如是否提到了所有用户提供的引用内容,并确保回答符合用户的实际需求,即使用指南问题解决。同时,保持回答结构清晰,分点列出,便于用户理解。</think>#### React Suspense 深度使用指南与常见问题解决方案 --- ### 一、核心应用场景 1. **代码拆分与懒加载** 通过 `React.lazy` + `Suspense` 实现组件级代码拆分,**动态加载非关键路径组件**,提升首屏性能[^1][^3]。 ```jsx const LazyComponent = React.lazy(() => import('./HeavyComponent')); function App() { return ( <Suspense fallback={<LoadingSpinner />}> <LazyComponent /> </Suspense> ); } ``` 2. **异步数据加载** 配合 `use` 钩子(React 18+)或第三方库(如 `SWR`, `React-Query`),**统一管理数据加载状态**: ```jsx function DataLoader() { const data = use(fetchData()); // 实验性API return <div>{data}</div>; } ``` --- ### 二、关键配置模式 #### 1. 路由级代码拆分(React Router v6)[^5] ```jsx import { useRoutes } from 'react-router-dom'; const routes = [ { path: '/', element: ( <Suspense fallback={<PageLoader />}> <HomePage /> </Suspense> ) }, { path: '/dashboard', element: ( <Suspense fallback={<DashboardSkeleton />}> {React.lazy(() => import('./Dashboard'))} </Suspense> ) } ]; function App() { return useRoutes(routes); } ``` #### 2. 并发模式控制(React 18+)[^4] ```jsx function SearchResults() { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); const [isPending, startTransition] = useTransition(); return ( <div> <input value={query} onChange={(e) => startTransition(() => setQuery(e.target.value))} /> <Suspense fallback={<ResultsSkeleton />}> <Results query={deferredQuery} /> </Suspense> {isPending && <ProgressIndicator />} </div> ); } ``` --- ### 三、常见问题解决方案 | 问题现象 | 解决方案 | 技术原理 | |---------|----------|----------| | **加载闪烁** | 使用 `startTransition` 包裹状态更新 | 延迟非关键渲染优先级[^4] | | **重复加载** | 添加 `webpackPrefetch: true` 魔法注释 | 预加载资源到缓存[^3] | | **SSR 异常** | 服务端使用 `@loadable/components` 替代 | 规避 React.lazy 的 SSR 限制 | | **错误边界失效** | 多层 Suspense 嵌套需逐级设置 ErrorBoundary | 错误冒泡机制限制 | --- ### 四、进阶优化方案 1. **预加载策略** 通过事件监听实现智能预加载: ```jsx const LazyModal = React.lazy(() => import('./Modal')); function App() { const [preloaded, setPreloaded] = useState(false); const handleHover = () => { if (!preloaded) { import('./Modal'); // 手动触发预加载 setPreloaded(true); } }; return ( <div onMouseEnter={handleHover}> <Suspense fallback={null}> {showModal && <LazyModal />} </Suspense> </div> ); } ``` 2. **流式渲染优化** 结合 `renderToPipeableStream` 实现服务端流式渲染: ```jsx // 服务端 const { pipe } = renderToPipeableStream( <App />, { onShellReady: () => pipe(res) } ); // 客户端 hydrateRoot(document, <App />); ``` --- ### 五、框架适配要点 | 技术栈 | 特殊配置 | 参考方案 | |--------|----------|----------| | **Next.js** | 修改 `next.config.js` 优化代码分割 | 配置 `experimental.lazyCompilation`[^3] | | **Gatsby** | 使用 `loadable-components` 插件 | 替代 React.lazy 的 SSR 方案 | | **Micro Frontends** | 主应用统一管理 Suspense 边界 | 避免样式/状态冲突 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值