react并发

46 阅读4分钟

并发模式不在于让单个任务跑的更快,而是通过智能调度中断机制,极大的提高用户体验,让用户感觉上更快更流畅,关键是因为并发模式改变了任务的执行方式,他通过时间切片优先级调度两项核心技术,解决了传统同步渲染的核心痛点:长时间阻塞线程

并发模式并不在于让单个任务跑的更快,有可能能通过频繁的调度时间反而变长一些了,主要作用是让用户的体验更好了,主要通过时间切片让任务可中断,通过智能调度,让程序可以优先处理紧急事件。

首先说时间切片,什么是时间切片?

React18的并发模式是建立在Fiber架构之上的,他能将渲染工作拆分成一个个小的单元,React在处理这些单元的时候,每隔几秒就会去检查一下是否有更重要的任务需要处理(比如用户输入、点击、动画等交互操作),如果有就暂停当前的渲染,让出主线程去响应用户的操作,然后再回来继续渲染,即使后台正在渲染一个庞大的列表,你的输入、点击等操作也能得到立即响应,页面不会“卡死”。

举个例子

import { useState, useDeferredValue, useTransition, useMemo, memo } from 'react';

// 一个渲染速度很慢的列表组件,用于模拟大量数据渲染
const SlowList = memo(({ query, onReset }) => {
  // 使用useMemo避免在query未改变时重复计算
  const items = useMemo(() => {
    console.log('过滤列表,查询词:', query);
    // 模拟昂贵的计算:根据查询词过滤一个大数据集
    const filteredItems = [];
    for (let i = 0; i < 5000; i++) {
      if (query.length === 0 || String(i).includes(query)) {
        filteredItems.push(<li key={i}>条目 #{i}</li>);
      }
    }
    return filteredItems;
  }, [query]); // 依赖项:当query改变时重新计算

  return <ul>{items}</ul>;
});

function SearchPage() {
  // 状态定义
  const [inputValue, setInputValue] = useState(''); // 输入框的实时值(高优先级)
  const [searchQuery, setSearchQuery] = useState(''); // 实际的搜索词(低优先级)
  
  // 使用useDeferredValue得到一个延迟版本的inputValue
  // 用于驱动SlowList的渲染,此更新可被中断
  const deferredInputValue = useDeferredValue(inputValue);
  
  // 使用useTransition来管理搜索提交的过渡状态
  // isPending 在过渡更新完成前为true
  const [isPending, startTransition] = useTransition();

  // 输入框变化处理函数
  const handleInputChange = (e) => {
    const value = e.target.value;
    setInputValue(value); // 紧急更新:立即显示用户输入
    
    // 使用startTransition将设置实际搜索词标记为非紧急更新
    // 如果用户连续输入,React会中断未完成的渲染,只处理最新的值
    startTransition(() => {
      setSearchQuery(value);
    });
  };

  // 重置搜索的处理函数(模拟另一个低优先级操作)
  const handleReset = () => {
    startTransition(() => {
      setInputValue('');
      setSearchQuery('');
    });
  };

  return (
    <div style={{ padding: '20px' }}>
      <h1>React 并发特性示例:搜索</h1>
      
      <div>
        <label htmlFor="search">搜索框(高优先级): </label>
        <input
          id="search"
          type="text"
          value={inputValue}
          onChange={handleInputChange}
          placeholder="输入数字进行过滤..."
        />
        <button onClick={handleReset} disabled={isPending}>
          重置搜索
        </button>
      </div>

      {/* 显示过渡状态 */}
      {isPending && <div style={{ color: 'blue' }}>⏳ 正在过滤结果...</div>}
      
      <div style={{ opacity: isPending ? 0.5 : 1 }}>
        <p>
          当前紧急输入: <strong>{inputValue}</strong>
          <br />
          用于列表过滤的值: <strong>{deferredInputValue}</strong>
          <br />
          来自useTransition的值: <strong>{searchQuery}</strong>
        </p>
        
        <h3>过滤结果(低优先级):</h3>
        {/* 
          SlowList组件使用deferredInputValue。
          当deferredInputValue"追赶"上inputValue时,列表会更新。
          这个渲染过程是可中断的。
        */}
        <SlowList query={deferredInputValue} onReset={handleReset} />
      </div>
    </div>
  );
}

export default SearchPage;

当您调用 startTransition时,本质上是将 setSearchQuery触发的​​整个组件树的更新任务​​标记为低优先级。React的调度器在安排这个任务进行渲染时,会将其拆解成一个个Fiber单元。如果在此期间用户再次输入,新的高优先级任务会抢占主线程,导致当前的低优先级渲染工作被中断,并且​​之前已经完成的部分Fiber计算结果会被丢弃​​,转而开始处理最新的输入值所对应的更新。

提问:如何中断?

正确回答

  • React 18 支持“可中断渲染”(并发特性)。要让某次更新可被中断,需要把它降级为低优先级。

  • 有两种常用方式:

    • useTransition / startTransition 把某次状态更新标记为低优先级(推荐回答)。
    • useDeferredValue 为某个值生成“低优先级的镜像”,用这个镜像去驱动慢组件。 两者区别
  • useTransition :你明确把某次 setState 置为低优先级,高优先级(比如输入)到来时,低优先级渲染会被打断并转去处理最新的。

  • useDeferredValue :对现有值生成一个“稍后才更新”的版本,用它驱动慢组件;高优先级到来时,慢组件的渲染同样会被打断。

简短示例
  • useTransition:

        startTransition(() => setFilteredList(expensiveFilter(input)))
    
  • useDeferredValue:

    const deferredQuery = useDeferredValue(query)
    <SlowList query={deferredQuery} />

面试官可能追问

  • 中断的是“渲染工作”,不是网络请求。若要中断请求,用 AbortController + fetch ,并在 useEffect 清理。
  • 并发只会丢弃进行中的旧渲染,转去渲染最新状态;不会把“错误的中间结果”提交到 DOM。
何时用哪一个:
  • 一个值同时用于“即时反馈”和“慢区域”→ useDeferredValue 更简单。
  • 需要精确控制某次更新为低优先级(不仅仅是一个值)→ useTransition 更合适。
可用回答模板

“在 React 18 里,我会把慢渲染降级为低优先级,让它在更高优先级到来时被中断。通常用 useTransition 标记某次状态更新为 transition,或用 useDeferredValue 生成一个延迟版的值去驱动耗时组件。数据请求的中断我会用 AbortController 。”