一、核心知识点梳理
- 并发更新(Concurrent Updates)
- 核心概念:传统 React 更新是 "同步且不可中断" 的,一旦开始渲染就会占用主线程直到完成,容易导致页面卡顿;而并发更新允许 React 中断、暂停、恢复或放弃正在进行的渲染,优先处理高优先级任务(比如用户输入),低优先级任务(比如列表渲染)可以被延后,这是 Concurrent Mode 的核心。
- 关键逻辑:React 通过 "优先级调度" 实现并发,给不同的更新任务分配不同的优先级(比如 ImmediatePriority、UserBlockingPriority、NormalPriority 等),调度器(Scheduler)会根据优先级决定先执行哪个任务,低优先级任务会被高优先级任务 "打断",等主线程空闲后再恢复。
- Suspense 工作原理
- 核心概念:Suspense 允许组件 "暂停渲染",直到某个异步操作(比如数据请求、代码加载)完成,期间显示 fallback 占位内容,异步操作完成后再渲染实际内容。
- 工作流程:
-
组件渲染时如果读取了一个 "未就绪" 的异步资源(比如封装了 Promise 的对象),会抛出这个 Promise;
-
React 捕获到这个 Promise 后,会暂停当前组件的渲染,显示 Suspense 定义的 fallback;
-
当 Promise 成功 resolve 后,React 会重新渲染该组件,此时异步资源已就绪,渲染实际内容;
如果 Promise reject,会触发错误边界。
-
useDeferredValue / useTransition
- useTransition:标记某个更新为 "低优先级过渡更新",不会阻塞用户交互,返回 [isPending, startTransition],startTransition 包裹的更新会被延后执行,isPending 标识更新是否还在进行。
- useDeferredValue:对一个值创建 "延迟版本",该值的更新会被延后,优先保证当前界面的响应性,常用于优化列表过滤等场景。
二、模拟 Suspense 基础功能实现
下面是一个极简版的 Suspense 模拟实现,核心还原 "暂停渲染 - 等待异步 - 渲染内容" 的核心逻辑
js
// 模拟 React 的 Suspense 核心逻辑
class SuspenseSimulator {
constructor() {
// 存储待解析的 Promise 和对应的重试渲染函数
this.pendingPromises = new Map();
// 根容器
this.root = null;
}
// 挂载根组件
render(element, container) {
this.root = container;
this._renderElement(element);
}
// 核心渲染逻辑:捕获异步资源的 Promise,显示 fallback,等待后重试
_renderElement(element) {
try {
// 渲染组件内容(如果组件读取未就绪的异步资源,会抛出 Promise)
const content = element.type({ fallback: element.props.fallback });
this.root.innerHTML = '';
this.root.appendChild(content);
} catch (e) {
console.log('catch', e)
// 捕获到 Promise,进入 Suspense 等待逻辑
if (e instanceof Promise) {
const promise = e;
// 显示 fallback 内容
this.root.innerHTML = '';
this.root.appendChild(element.props.fallback);
// 避免重复监听同一个 Promise
if (!this.pendingPromises.has(promise)) {
this.pendingPromises.set(promise, () => {
this._renderElement(element);
this.pendingPromises.delete(promise);
});
// 等待 Promise 完成后,重新渲染
promise.then(() => {
const retryRender = this.pendingPromises.get(promise);
retryRender && retryRender();
}).catch(err => {
console.error('Suspense 异步资源加载失败:', err);
this.root.innerHTML = '<div>加载失败,请重试</div>';
this.pendingPromises.delete(promise);
});
}
} else {
// 非 Promise 异常,直接抛出
throw e;
}
}
}
}
// ---------------------- 测试用例 ----------------------
// 1. 模拟异步
function fetchData() {
// 模拟 2 秒后返回数据
const promise = new Promise(resolve => {
setTimeout(() => {
resolve({ name: 'React Suspense 模拟实现', author: '笔记' });
}, 2000);
});
// 封装成 Suspense 可识别的"未就绪资源":读取时抛出 Promise
let status = 'pending';
let result = null;
return {
read() {
if (status === 'pending') {
throw promise; // 抛出 Promise,触发 Suspense
} else if (status === 'error') {
throw new Error('数据加载失败');
} else {
return result;
}
},
// 触发数据加载
load() {
promise.then(data => {
status = 'success';
result = data;
}).catch(() => {
status = 'error';
});
}
};
}
const dataResource = fetchData();
// 2. 模拟依赖异步资源的组件
const DataComponent = ({ fallback }) => {
dataResource.load(); // 触发数据加载
const data = dataResource.read(); // 读取数据,未就绪时抛出 Promise
// 渲染实际内容
const div = document.createElement('div');
div.style.padding = '20px';
div.innerHTML = `
<h3>${data.name}</h3>
<p>${data.author}</p>
<p>加载完成时间:${new Date().toLocaleTimeString()}</p>
`;
return div;
};
// 3. 模拟 Suspense 组件
const Suspense = ({ fallback, children }) => {
return {
type: children,
props: { fallback }
};
};
// 4. 初始化并运行模拟
const suspense = new SuspenseSimulator();
// 创建 fallback 元素
const fallback = document.createElement('div');
fallback.innerHTML = '<div style="color: #666;">加载中... 🌀</div>';
// 挂载 Suspense 组件
suspense.render(
Suspense({
fallback: fallback,
children: DataComponent
}),
document.getElementById('root')
);
核心逻辑解释:
- SuspenseSimulator 类模拟 React 核心渲染逻辑,捕获组件抛出的 Promise;
- fetchData 模拟异步资源,read 方法在资源未就绪时抛出 Promise,触发 Suspense;
- 组件挂载后先显示 fallback(加载中),2 秒后异步资源就绪,重新渲染实际内容;
三、要点总结
- 并发模式核心价值
- 解决 "长任务阻塞主线程" 问题,提升用户交互的响应性;
- 核心是 "优先级调度",而非 "多线程"(React 仍运行在单线程)。
- Suspense 核心
- 本质是 "渲染时的异常捕获(Promise)+ 异步重试";
- 不仅能用于数据请求,还能用于代码分割(React.lazy + Suspense)。
- useDeferredValue/useTransition 区别
- useTransition:主动标记 "低优先级更新",控制更新行为;
- useDeferredValue:被动创建 "延迟值",控制值的更新时机;
两者都是基于并发更新的优先级调度实现,最终目的都是不阻塞高优先级任务。