并发模式不在于让单个任务跑的更快,而是通过智能调度和中断机制,极大的提高用户体验,让用户感觉上更快更流畅,关键是因为并发模式改变了任务的执行方式,他通过时间切片和优先级调度两项核心技术,解决了传统同步渲染的核心痛点:长时间阻塞线程
并发模式并不在于让单个任务跑的更快,有可能能通过频繁的调度时间反而变长一些了,主要作用是让用户的体验更好了,主要通过时间切片让任务可中断,通过智能调度,让程序可以优先处理紧急事件。
首先说时间切片,什么是时间切片?
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 。”