通过并发渲染提升应用性能
并发渲染是React 18的一项功能,可助力框架同时准备多个用户界面版本。借助并发渲染,React可以根据需要中断、暂停、放弃或恢复渲染工作。可以将更新标记为紧急或非紧急,从而让React能够在不阻塞主线程的情况下,一次处理多个状态更新。React会优先处理紧急的用户交互(键入、点击、按下),同时在后台处理不太紧急的更新(大型屏幕更新、数据获取)。
通过并发渲染,Vega应用能够:
- 确保播放和交互流畅: 进行重量级更新期间,用户界面控件和媒体仍保持响应
- 优化后台处理: 数据加载或计算不会导致界面卡顿
- 改善导航体验: 即便执行复杂的渲染任务,屏幕切换依然流畅自如
请观看此视频(附带中文字幕),大致了解如何在Vega中执行并发渲染:
挂钩、组件和辅助函数
有两个React挂钩(useTransition和useDeferredValue)可用于管理更新和控制应用的性能。请务必将其与组件备忘功能搭配使用,以避免不必要的重新渲染。
React还提供了Suspense组件,用于在重量级组件进行渲染的同时显示备用组件。lazy辅助函数可以延迟加载重量级组件,并使其保持已缓存状态,以备后续更新。
useTransition
useTransition挂钩可以将状态更新标记为立即更新或过渡性更新(优先级较低且可中断)。这就解决了搜索界面中的一个常见难题:筛选操作可能会导致用户界面更新延迟。现在,您可以降低筛选的优先级,同时让搜索输入保持响应,而无需使用防抖等技术。
该挂钩会返回两个值:
isPending: 一个布尔值,指示是否有低优先级更新正在等待处理,适用于显示加载状态startTransition: 一个函数,用于包装要视为非紧急的状态更新
示例:
在这个代码示例中,虽然搜索输入通过setQuery实现即时更新,但筛选操作通过startTransition推迟执行,过渡期间会显示加载指示器,准备就绪后会显示结果。
const SearchComponent = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (query) => {
setQuery(query); // 更新搜索输入
startTransition(() => {
setResults(performSearch(query); // 推迟搜索操作
});
};
return (
<View>
<TextInput
value={query}
onChange={handleSearch}
/>
{isPending && <LoadingIndicator />}
<SearchResults results={results} />
</View>
);
};
这段代码表明startTransition不会延迟函数的执行,只会将setResults更新标记为低优先级。React会先处理setQuery(高优先级队列),再处理setResults(低优先级队列)。
如果无法直接使用该挂钩,请直接使用startTransition函数。
适用情况: 搜索界面、导航、大型列表更新(由您控制组件中的状态变量)。
不适用情况: 您无法直接控制其状态的组件(此时应改用useDeferredValue)。
useDeferredValue
useDeferredValue挂钩可以推迟更新来自属性或外部来源的值,待关键更新完成之后再更新这些值。例如,组件收到搜索结果后,可以立即更新标头中的搜索词,同时推迟更繁重的结果预览更新工作。
适用情况: 当您没有状态变量的访问权限时,会触发耗时计算或异步操作的属性。
不适用情况: 由您控制状态变量的组件(此时应改用useTransition)
Suspense和lazy
Suspense组件会在代码或数据加载(准备就绪)期间显示备用内容。lazy函数会按需导入组件(在需要时获取代码)。两者可以单独使用,也可组合使用。
单独使用Suspense获取数据
在这个示例中,Suspense会显示加载占位符 (MediaSkeleton),MediaList则会提取和渲染类型数据:
import { MediaAPI, Media, MediaCard } from "./api/mediaApi.ts";
type Props = { genre: string };
export const MediaList = ({ genre }: Props) => {
const [media, setMedia] = useState<Media[]>([]);
useEffect(() => {
// Promise解析时间较长
MediaAPI.getGenre(genre)
.then(setMedia)
.catch((e) => console.error(e));
}, [genre]);
if (media.length===0) return null;
return media.map((media: Media) => (
<MediaCard key={media.id} media={media} />
));
};
搭配使用lazy和Suspense,实现代码拆分并显示内置加载状态
在这个示例中,MassiveComponent会在LazyLoadedScreen渲染时导入,Suspense则会显示MediaSkeleton占位符:
const MassiveComponent = lazy(() => import('./MassiveComponent'));
export const LazyLoadedScreen = () => {
return (
<View style={styles.container}>
<Suspense fallback={<MediaSkeleton style={styles.skeleton} />}>
<MassiveComponent />
</Suspense>
</View>
);
};
实现并发渲染
请按照以下步骤实现并发渲染:
步骤1: 添加组件级备忘功能
备忘功能可避免不必要的重新渲染和计算。对于使用useTransition() 或useDeferredValue() 的组件的子组件来说,这一点至关重要。如果没有备忘功能,则子组件会在父级组件更新后重新渲染,从而失去并发渲染的优势。
不使用备忘功能:
在这个示例中,SearchComponent展示了备忘功能如何影响使用useTransition进行的渲染。如果不使用备忘功能,则即使结果没有变化,SearchResults也会在每次按键时重新渲染。
const SearchComponent = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (query) => {
setQuery(query); // 更新搜索输入
startTransition(() => {
setResults(performSearch(query)); // 推迟搜索操作
});
};
// SearchResult会在每次按键时重新渲染
const SearchResults = ({results}) => {
return results.map(result => <Card data={result} />);
};
return (
<View>
<TextInput
value={query}
onChange={handleSearch}
/>
{isPending && <LoadingIndicator />}
<SearchResults results={results} />
</View>
);
};
使用备忘功能:
如果使用memo,则SearchResults仅在结果发生变化时重新渲染。
const SearchComponent = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (query) => {
setQuery(query); // 更新搜索输入
startTransition(() => {
setResults(performSearch(query)); // 推迟搜索操作
});
};
// SearchResults仅在结果发生变化时重新渲染
const SearchResults = memo(({results}) => {
return results.map(result => <Card data={result} />);
});
return (
<View>
<TextInput
value={query}
onChange={handleSearch}
/>
{isPending && <LoadingIndicator />}
<SearchResults results={results} />
</View>
);
};
步骤2: 应用按优先级进行的更新
对于键入、点击、滚动浏览等交互操作,用户需要即时反馈,而数据处理或用户界面更新则可以稍后进行。可以使用useTransition和useDeferredValue来确定更新的优先级。
不按优先级更新:
const SearchComponent = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleSearch = (query) => {
setQuery(query); // 更新搜索输入
setResults(performSearch(query); // 在搜索期间阻塞用户界面
};
return (
<View>
<TextInput
value={query}
onChange={handleSearch}
/>
<SearchResults results={results} />
</View>
);
};
每次输入更新 (setQuery) 后,耗时操作 (performSearch) 会立即运行并阻塞用户界面,直至两次更新都完成。用户会在键入时遇到延迟,因为每次按键都需要等待搜索操作完成。
按优先级更新:
const SearchComponent = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (query) => {
setQuery(query); // 更新搜索输入
startTransition(() => {
setResults(performSearch(query); // 推迟搜索操作
});
};
return (
<View>
<TextInput
value={query}
onChange={handleSearch}
/>
{isPending && <LoadingIndicator />}
// 请勿使用备忘功能!!
<SearchResults results={results} />
</View>
);
};
现在,耗时操作 (performSearch) 以过渡状态运行,因此输入更新 (setQuery) 可以立即完成。用户可以流畅键入,同时搜索结果在后台更新,并且在更新等待处理时会显示加载指示器。
测试与验证
可以使用以下工具测试并发渲染的实现:
React DevTools分析器
React DevTools Profiler可用于测试备忘功能和按优先级更新的实现:
- 在Chrome中打开React DevTools。
- 选择Profiler标签页。
- 单击录制按钮。
- 执行测试交互(输入搜索词、滚动浏览列表)。
- 停止记录。
- 查看火焰图中是否存在不必要的重新渲染和过渡标记。
Vega App KPI可视化工具
测试用户界面响应速度时,请使用Vega App KPI可视化工具。
要运行KPI可视化工具,可执行以下命令:
kepler exec perf kpi-visualizer --app-name=[app-name] --kpi ui-fluidity --test-scenario <python test scenario with appium>
查看流畅度评分是否高于99%,以确认并发渲染的实现能够保持流畅的用户界面交互体验。
相关主题
Last updated: 2025年11月5日

