标题说"用了三年"是夸张的说法,React 18是2022年3月才正式发布的,到本文写作时实际用了不到一年。

但在React 18的alpha和beta阶段,我就已经开始尝试并发特性了。本文分享我对React 18并发特性的理解和经验总结。

一、React 18并发特性概述

1. 什么是并发渲染

React 18最大的变化,是引入了并发渲染(Concurrent Rendering)。

并发渲染的核心思想:

  • 渲染可以被中断
  • React可以暂停、继续、甚至放弃一次渲染
  • 高优先级的更新可以打断低优先级的更新
  • 让界面保持响应,不卡顿

在React 18之前,渲染是同步的、不可中断的。一旦开始渲染,就必须完成,期间用户交互会被阻塞。

2. 主要的并发特性

React 18提供了几个并发特性:

  • useTransition:标记非紧急更新
  • useDeferredValue:延迟更新某个值
  • Suspense:在数据加载时显示fallback
  • startTransition:API版本的useTransition
  • 自动批处理:所有更新自动批处理,不只是事件处理函数中

这些特性,让我们可以更精细地控制渲染优先级。

二、我踩过的坑

1. 坑一:以为并发渲染会自动让应用变快

最开始,我以为升级到React 18,应用就会自动变快。

结果发现:

  • 大部分应用,升级后性能没有明显变化
  • 并发渲染不是银弹
  • 如果没有使用并发特性,和React 17差不多
  • 并发渲染是提供了能力,需要主动使用

明白:并发渲染是基础能力,需要配合useTransition等API才能发挥作用。

2. 坑二:滥用useTransition

后来,我开始用useTransition,但用得太多了。

  • 什么更新都包在startTransition里
  • 结果反而导致界面更新延迟
  • 用户输入后,界面要等一会儿才更新
  • 体验反而变差了

明白:useTransition只适合非紧急的更新。紧急的更新(比如用户输入)不应该用transition。

3. 坑三:不理解Suspense的边界

用Suspense的时候,我踩了不少坑。

  • 不知道Suspense的边界在哪里
  • 嵌套Suspense的行为不符合预期
  • fallback闪烁的问题
  • 数据获取和Suspense的配合

明白:Suspense需要和支持Suspense的数据获取库配合使用,不是随便就能用的。

4. 坑四:忽略了自动批处理的影响

React 18的自动批处理,让我踩了坑。

  • 在setTimeout、Promise中多次setState,现在会被批处理
  • 有些依赖同步更新的代码出了问题
  • 比如在setState后立即读取state,读到的是旧值
  • 需要用useEffect或函数式更新

明白:自动批处理是好事,但需要理解它的行为,调整代码。

三、我明白的道理

1. 道理一:并发渲染不是性能优化,而是体验优化

这是我最重要的体会。

并发渲染的目的,不是让渲染更快,而是让界面更流畅。

  • 它不能减少渲染的总时间
  • 但它可以让高优先级的更新先响应
  • 比如用户输入时,搜索结果的更新可以延后
  • 界面保持响应,用户体验更好

并发渲染优化的是体验,不是绝对性能。

2. 道理二:区分紧急和非紧急更新

使用并发特性的关键,是区分紧急和非紧急更新。

紧急更新:

  • 用户输入
  • 按钮点击
  • 直接交互
  • 需要立即响应

非紧急更新:

  • 搜索结果过滤
  • 列表渲染
  • 数据加载后的更新
  • 可以延迟的更新

紧急更新用正常的setState,非紧急更新用startTransition。

3. 道理三:useTransition的正确用法

useTransition的正确用法:

function SearchResults() {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    // 紧急更新:输入框立即响应
    setQuery(e.target.value);
    
    // 非紧急更新:搜索结果可以延迟
    startTransition(() => {
      setSearchQuery(e.target.value);
    });
  };

  return (
    <div>
      <input value={query} onChange={handleChange} />
      {isPending && <Spinner />}
      <Results query={searchQuery} />
    </div>
  );
}

关键点:

  • 输入框的值,用紧急更新
  • 搜索结果,用transition
  • isPending可以用来显示加载状态

4. 道理四:useDeferredValue适合什么场景

useDeferredValue,适合延迟某个值的更新。

function App() {
  const [text, setText] = useState('');
  const deferredText = useDeferredValue(text);

  return (
    <div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <ExpensiveComponent text={deferredText} />
    </div>
  );
}

和useTransition的区别:

  • useTransition:你控制更新的时机
  • useDeferredValue:React控制值的延迟
  • useDeferredValue适合你无法控制更新来源的情况

5. 道理五:Suspense不是数据获取库

Suspense本身不获取数据,它只是一个机制。

  • Suspense配合支持Suspense的数据获取库使用
  • 比如React Query、Relay、SWR(部分支持)
  • Suspense让你可以像同步代码一样写异步逻辑
  • 在数据加载时,自动显示fallback

不要试图自己用Suspense实现数据获取,用成熟的库。

6. 道理六:并发渲染需要思维转变

使用React 18并发特性,需要思维转变。

  • 以前:渲染是同步的,更新立即执行
  • 现在:渲染可以被中断,更新可能被延迟
  • 以前:setState后立即重新渲染
  • 现在:setState可能被批处理、被中断、被延迟

代码要能适应这种不确定性,不能依赖渲染的同步性。

四、实际应用场景

1. 搜索过滤

最常见的场景:搜索过滤。

  • 用户输入时,输入框要立即响应
  • 过滤大列表时,可以延迟
  • 用useTransition,体验流畅
function FilterList() {
  const [input, setInput] = useState('');
  const [filter, setFilter] = useState('');
  const [isPending, startTransition] = useTransition();

  return (
    <div>
      <input
        value={input}
        onChange={(e) => {
          setInput(e.target.value);
          startTransition(() => {
            setFilter(e.target.value);
          });
        }}
      />
      {isPending && <div>过滤中...</div>}
      <List filter={filter} />
    </div>
  );
}

2. 标签页切换

标签页切换,也是一个好场景。

  • 点击标签,立即切换标签高亮
  • 内容加载,可以延迟
  • 用Suspense显示加载状态
function Tabs() {
  const [tab, setTab] = useState('home');

  return (
    <div>
      <button onClick={() => setTab('home')}>首页</button>
      <button onClick={() => setTab('profile')}>个人</button>
      <Suspense fallback={<Spinner />}>
        {tab === 'home' && <Home />}
        {tab === 'profile' && <Profile />}
      </Suspense>
    </div>
  );
}

3. 表单提交

表单提交时,可以用transition。

  • 提交按钮的状态,立即更新
  • 表单数据的处理,可以延迟
  • 防止重复提交

4. 路由切换

路由切换,是并发特性的大用场。

  • 点击链接,立即有反馈
  • 新页面的加载,可以用Suspense
  • 旧页面可以保持交互
  • React Router已经支持

五、性能优化建议

1. 不要过早优化

  • 先写正常的代码
  • 遇到性能问题,再用并发特性
  • 不要为了用而用
  • 大部分场景,不需要并发特性

2. 配合React.memo和useMemo

并发特性和传统优化配合使用:

  • React.memo:避免不必要的重渲染
  • useMemo:缓存计算结果
  • useCallback:缓存函数
  • 并发特性:控制渲染优先级

3. 测量,不要猜测

  • 用React DevTools Profiler分析
  • 找到真正的性能瓶颈
  • 针对性优化
  • 优化后再测量

4. 渐进式采用

  • 不需要一次性全部用上
  • 可以先在一个组件里试用
  • 验证效果后再推广
  • 保持代码的可维护性

六、写在最后

React 18的并发特性,是React的一次重大升级。

它不是银弹,不能让所有应用自动变快。但在合适的场景下,它能显著提升用户体验。关键是理解它的原理,区分紧急和非紧急更新,在正确的地方使用正确的API。

2022年,React 18刚发布不久,并发特性还在发展中。很多库还在适配,最佳实践还在形成。但并发渲染是未来的方向,值得学习和尝试。

最后,用一句话总结:"React 18并发特性,不是性能优化,而是体验优化。理解原理,区分优先级,在正确的场景使用,才能发挥它的价值。"

愿你用好React 18,打造流畅的用户体验。