as

Settings
Sign out
Notifications
Alexa
亚马逊应用商店
AWS
文档
Support
Contact Us
My Cases
新手入门
设计和开发
应用发布
参考
支持

通过并发渲染提升应用性能

通过并发渲染提升应用性能

并发渲染是React 18的一项功能,可助力框架同时准备多个用户界面版本。借助并发渲染,React可以根据需要中断、暂停、放弃或恢复渲染工作。可以将更新标记为紧急或非紧急,从而让React能够在不阻塞主线程的情况下,一次处理多个状态更新。React会优先处理紧急的用户交互(键入、点击、按下),同时在后台处理不太紧急的更新(大型屏幕更新、数据获取)。

通过并发渲染,Vega应用能够:

  • 确保播放和交互流畅: 进行重量级更新期间,用户界面控件和媒体仍保持响应
  • 优化后台处理: 数据加载或计算不会导致界面卡顿
  • 改善导航体验: 即便执行复杂的渲染任务,屏幕切换依然流畅自如

请观看此视频(附带中文字幕),大致了解如何在Vega中执行并发渲染:

挂钩、组件和辅助函数

有两个React挂钩(useTransitionuseDeferredValue)可用于管理更新和控制应用的性能。请务必将其与组件备忘功能搭配使用,以避免不必要的重新渲染。

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} />
  ));
};

搭配使用lazySuspense,实现代码拆分并显示内置加载状态

在这个示例中,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: 应用按优先级进行的更新

对于键入、点击、滚动浏览等交互操作,用户需要即时反馈,而数据处理或用户界面更新则可以稍后进行。可以使用useTransitionuseDeferredValue来确定更新的优先级。

不按优先级更新:

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可用于测试备忘功能和按优先级更新的实现:

  1. 在Chrome中打开React DevTools
  2. 选择Profiler标签页。
  3. 单击录制按钮。
  4. 执行测试交互(输入搜索词、滚动浏览列表)。
  5. 停止记录。
  6. 查看火焰图中是否存在不必要的重新渲染和过渡标记。

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日