Transition 核心概念:Transition是一种标记非紧急任务更新的机制,它允许React在用户交互(如输入)期间保持界面的响应,同时准备后台更新
主要特点:
- 区分优先级:可以将更新分为紧急非紧急任务
- 可中断渲染:Transition更新可以被更紧急的交互打断
- 自动降级:在不支持并发环境中自动回退到同步渲染
场景:
在Input中输入搜索内容,过滤列表,由于数据量比较大10万条数据导致页面卡死
那如何处理此瓶颈:10万条数据分页还是利用虚拟滚动实现假性分页。
如果把同步更新任务变成异步更新任务是不是就可以解决问题。Transition就可以处理多个并发任务
Input表单的并发任务分别为:
- 更新Input的内容,同时会触发更新任务(高优先级的任务)
- Input内容改变,过滤列表,重新渲染也是一个任务(低优先级任务)
这里的任务可以分为紧急优先级任务和非紧急任务
紧急任务就是当用户改变Input框的内容时候要立马能够看到更新后的内容,不然就会有卡顿、延迟。会有一种极差的视觉体验
非紧急任务是当input输入内容后,过滤列表并重新渲染列表,这个过程如果有延迟,用户也是可以接受的
什么是紧急任务:直接影响用户即时交互的界面更新,在本例中,输入框value的更新、输入框的光标位置、焦点状态等
什么是非紧急任务:可以稍后处理的计算密集型或网络请求
为什么紧急:每次用户操作后需要立即看到输入后的反馈,否则顿感卡顿,如果延迟处理,会导致输入内容与显示不一致,糟糕的用户体验
为什么非紧急:用户能够容忍短暂的结果延迟,如果与输入框竞争资源,反而会导致输入卡顿
Transition基本用法:
javascript
import { startTransition } from 'react';
// 在事件处理中
function handleInputChange(e) {
const value = e.target.value;
// 紧急更新:立即更新输入框
setInputValue(value);
// 非紧急更新:标记为Transition
startTransition(() => {
setSearchQuery(value); // 可能触发大量计算的更新
});
}
使用 useTransition Hook
javascript
import { useTransition } from 'react';
function SearchBox() {
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const value = e.target.value;
setInputValue(value);
startTransition(() => {
setSearchQuery(value);
});
};
return (
<div>
<input onChange={handleChange} />
{isPending && <Spinner />} {/* 显示过渡状态 */}
</div>
);
}
上述案例完整代码:
javascript
import { useState, useTransition } from 'react';
function SearchComponent() {
const [inputValue, setInputValue] = useState('');
const [searchResults, setSearchResults] = useState([]);
const [isPending, startTransition] = useTransition();
// 实际项目中实现的搜索函数
async function performHeavySearch(keyword) {
const response = await fetch(`/api/search?q=${keyword}`);
const data = await response.json();
return data.items; // 根据实际API结构调整
}
const handleChange = async (e) => {
const value = e.target.value;
setInputValue(value);
startTransition(async () => {
const results = await performHeavySearch(value);
setSearchResults(results);
});
};
return (
<div>
<input value={inputValue} onChange={handleChange} />
{isPending ? (
<div>Searching...</div>
) : (
<ul>
{searchResults.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)}
</div>
);
}
典型案例:
- 搜索输入:输入时保持输入框响应,搜索结果稍后显示
- 标签页切换:点击切换标签时立即显示激活状态,内容稍后加载
- 大数据渲染:优先渲染可见部分,其他内容渐进加载
与Suspense的结合使用:Transition 可以与 Suspense 完美配合,实现流畅的异步加载体验:
javascript
import { Suspense, useTransition } from 'react';
function App() {
const [resource, setResource] = useState(initialResource);
const [isPending, startTransition] = useTransition();
function fetchNewData() {
startTransition(() => {
setResource(fetchData()); // 返回一个Suspense兼容的资源
});
}
return (
<div>
<button
onClick={fetchNewData}
disabled={isPending}
>
{isPending ? 'Loading...' : 'Load Data'}
</button>
<Suspense fallback={<Spinner />}> {/* Spinner 显示过渡状态 */}
<DataDisplay resource={resource} />
</Suspense>
</div>
);
}
React中Suspense和核心功能:
1、作用:
- 用在子组件(懒加载或异步数据请求)完成加载前显示一个后备方案(fallback UI),例如加载动画或者占位符
- 支持代码分割(通过React.lazy)和异步数据加载(需支持Suspense的库,如react query或relay)
2、关键特性:
- 代码分割:与react.lazy结合,实现组件按需加载
- 数据加载:需依赖Suspense库,原生react不支持异步数据Suspense
- 嵌套使用:允许逐步加载内容,优化用户体验
3、示例:
javascript
<Suspense fallback={<Loading />}>
<LazyComponent /> // 通过 React.lazy 加载
</Suspense>
与vue中的Suspense
1、作用
- 处理异步组件或异步setup函数加载状态,显示后备内容
- 支持任意异步逻辑(如数据请求或动态导入组件),不限于组件级懒加载
2、关键特性
- 插槽设计:通过#default和#fallback插槽管理内容
- 事件监听:提供pending、resolve等事件、便于控制加载状态
- 嵌套支持:子组件的异步依赖会触发父级Suspense的fallback
3、示例
javascript
<Suspense>
<template #default>
<AsyncComponent /> // 异步组件或含 async setup 的组件
</template>
<template #fallback>
<Loading />
</template>
</Suspense>
主要区别:
特性 | React Suspense | Vue Suspense |
---|---|---|
支持范围 | 主要针对组件懒加载、异步数据请求、数据需要第三方支持 | 支持任意异步逻辑(组件、数据等) |
API 设计 | 通过fallback prop定义占位内容 | 使用插槽(#default 和 #fallback |
嵌套行为 | 内部Suspense优先处理 | 父级的Suspense等子异步依赖完成再处理 |
事件监听 | 无内置事件 | 通过pending、resolve、fallback等事件 |
vue3中Suspense中事件用法
javascript
<template>
<Suspense
@pending="onPending"
@resolve="onResolve"
@fallback="onFallback"
>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
);
function onPending() {
console.log('开始异步加载');
// 可以在这里显示全局加载状态
// 发送分析事件
analytics.track('DashboardLoadStart');
}
function onResolve() {
console.log('异步加载完成');
// 可以在这里隐藏全局加载状态
// 发送性能数据
analytics.track('DashboardLoaded', { duration: loadTime });
}
function onFallback() {
console.log('Fallback内容被展示');
// 可以记录fallback显示时间等
}
</script>
备注:这里面可以用作性能监控,当开始的时候发送事件分析,当结束时候获得性能数据
代码分割详解:
代码分割是前端性能优化中的重要技术。
代码分割是将整个应用分割成多个小块,然后按需加载,而不是一次性加载所有代码,这可以:
- 显著减少初始加载时间
- 降低首屏资源体积
- 通过应用交互响应速度
传统代码分割问题:
javascript
// 传统动态导入方式
import("./MyComponent.js").then(module => {
// 组件加载完成后才能使用
});
- 需要手动处理加载状态
- 容易导致布局跳动或空白
- 代码组织不够直观
Suspense如何分割代码
React:
javascript
const MyComponent = React.lazy(() => import('./MyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<MyComponent /> {/* 被代码分割的组件 */}
</Suspense>
);
}
Vue3:
javascript
<script setup>
const AsyncComp = defineAsyncComponent(() =>
import('./MyComponent.vue')
)
</script>
<template>
<Suspense>
<template #default>
<AsyncComp />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
- 动态导入组件
- 导入过程中,Suspense显示fallback内容
- 加载完成后,替换为实际组件
技术实现细节:
Webpack的代码分割:当使用import()语法时,打包工具会自动:
- 将目标分割成一个独立的chunk文件
- 生成运行时候加载逻辑
- 在需要时通过 JSONP动态获取
Suspense的协调机制:
- React/Vue会自动追踪异步组件加载状态
- 在模块加载期间暂停渲染
- 加载完成后重新触发渲染
为什么需要Suspense
无Suspense | 有Suspense |
---|---|
需要手动维护loading | 统一处理loading |
多个异步加载时状态复杂 | 支持嵌套异步依赖 |
错误处理困难 | 与错误边界(Error Boundaries)天然集成 |
性能优化技巧:
预加载策略:
javascript
// 鼠标悬停时预加载
function onLinkHover() {
import('./ComponentToPrefetch');
}
命名chunks:
javascript
const Component = lazy(() => import(
/* webpackChunkName: "specific-name" */
'./Component'
));
Suspense嵌套:
javascript
<Suspense fallback={<AppLoader />}>
<Layout>
<Suspense fallback={<SidebarLoader />}>
<Sidebar />
</Suspense>
</Layout>
</Suspense>