当前位置: 首页 > news >正文

React 18 渲染机制优化:解决浏览器卡顿的三种方案

前言

React 18 带来了革命性的并发渲染机制,让我们能够更好地处理大量计算任务而不阻塞用户界面。本文将通过一个经典的面试题来深入探讨几种解决浏览器卡顿的方案,并分析 React 18 的内部实现原理。

问题场景

假设我们有一个函数 runTask,需要处理大量耗时任务:

function runTask(tasks) {while (tasks.length > 0) {const task = tasks.shift();task();}
}// 模拟耗时任务
function createSlowTask(id) {return function () {const start = performance.now();// 模拟 3ms 的计算任务while (performance.now() - start < 3) {// 忙等待}console.log(`Task ${id} completed`);};
}// 创建 100 个任务
const tasks = Array.from({ length: 100 }, (_, i) => createSlowTask(i + 1));

让我们创建一个完整的 HTML 页面来演示这个问题:

<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>任务执行优化 Demo</title><style>body {font-family: Arial, sans-serif;max-width: 800px;margin: 0 auto;padding: 20px;}.animation-container {width: 400px;height: 50px;border: 1px solid #ccc;position: relative;margin: 20px 0;}.moving-ball {width: 20px;height: 20px;background-color: #007bff;border-radius: 50%;position: absolute;top: 15px;animation: moveBall 3s linear infinite;}@keyframes moveBall {0% {left: 0;}50% {left: 360px;}100% {left: 0;}}button {padding: 10px 20px;margin: 10px;font-size: 16px;cursor: pointer;}.status {margin: 10px 0;padding: 10px;background-color: #f8f9fa;border-radius: 4px;}</style></head><body><h1>任务执行优化演示</h1><div class="animation-container"><div class="moving-ball"></div></div><div class="status" id="status">准备就绪</div><button onclick="runBlockingTask()">执行阻塞任务</button><button onclick="runOptimizedTask1()">requestIdleCallback 优化</button><button onclick="runOptimizedTask2()">requestAnimationFrame 优化</button><button onclick="runOptimizedTask3()">MessageChannel 优化</button><script>// 创建耗时任务function createSlowTask(id) {return function () {const start = performance.now();while (performance.now() - start < 3) {// 模拟计算密集型任务}console.log(`Task ${id} completed`);};}// 原始阻塞版本function runBlockingTask() {document.getElementById('status').textContent = '执行阻塞任务中...';const tasks = Array.from({ length: 100 }, (_, i) => createSlowTask(i + 1));setTimeout(() => {const start = performance.now();while (tasks.length > 0) {const task = tasks.shift();task();}const end = performance.now();document.getElementById('status').textContent = `阻塞任务完成,耗时: ${(end - start).toFixed(2)}ms`;}, 10);}</script></body>
</html>

当你点击"执行阻塞任务"按钮时,会发现小球的动画明显卡顿,这就是我们要解决的问题。

方案一:使用 requestIdleCallback

requestIdleCallback 允许我们在浏览器空闲时执行任务,避免阻塞主线程:

function runTaskWithIdleCallback(tasks) {if (tasks.length === 0) {document.getElementById('status').textContent = 'requestIdleCallback 任务全部完成';return;}requestIdleCallback((deadline) => {document.getElementById('status').textContent = `requestIdleCallback 执行中,剩余 ${tasks.length} 个任务`;while (deadline.timeRemaining() > 0 && tasks.length > 0) {const task = tasks.shift();task();}if (tasks.length > 0) {runTaskWithIdleCallback(tasks); // 递归处理剩余任务} else {document.getElementById('status').textContent = 'requestIdleCallback 任务全部完成';}});
}function runOptimizedTask1() {const tasks = Array.from({ length: 100 }, (_, i) => createSlowTask(i + 1));runTaskWithIdleCallback(tasks);
}

方案二:使用 requestAnimationFrame

requestAnimationFrame 与浏览器的刷新率同步,我们可以利用每一帧的时间来处理任务:

function runTaskWithRAF(tasks) {if (tasks.length === 0) {document.getElementById('status').textContent = 'requestAnimationFrame 任务全部完成';return;}requestAnimationFrame(() => {const start = performance.now();const frameTime = 16.6; // 60fps 下每帧约 16.6msdocument.getElementById('status').textContent = `requestAnimationFrame 执行中,剩余 ${tasks.length} 个任务`;while (performance.now() - start < frameTime && tasks.length > 0) {const task = tasks.shift();task();}if (tasks.length > 0) {runTaskWithRAF(tasks); // 递归处理剩余任务} else {document.getElementById('status').textContent = 'requestAnimationFrame 任务全部完成';}});
}function runOptimizedTask2() {const tasks = Array.from({ length: 100 }, (_, i) => createSlowTask(i + 1));runTaskWithRAF(tasks);
}

方案三:使用 MessageChannel 实现时间切片

这种方案类似于 React 18 的内部实现,使用 MessageChannel 来调度任务:

// 基于 MessageChannel 的调度器
class TaskScheduler {constructor() {this.taskQueue = [];this.isScheduled = false;this.frameInterval = 5; // 每个时间片 5msthis.startTime = 0;// 创建 MessageChannelthis.channel = new MessageChannel();this.port = this.channel.port2;this.channel.port1.onmessage = this.performWork.bind(this);}scheduleTask(callback) {this.taskQueue.push(callback);if (!this.isScheduled) {this.isScheduled = true;this.requestHostCallback();}}requestHostCallback() {this.port.postMessage(null);}performWork() {this.startTime = performance.now();let hasMoreWork = this.workLoop();if (hasMoreWork) {this.requestHostCallback();} else {this.isScheduled = false;document.getElementById('status').textContent = 'MessageChannel 任务全部完成';}}workLoop() {while (this.taskQueue.length > 0) {if (this.shouldYield()) {return true; // 还有更多工作}const task = this.taskQueue.shift();task();}return false; // 工作完成}shouldYield() {return performance.now() - this.startTime >= this.frameInterval;}updateStatus() {document.getElementById('status').textContent = `MessageChannel 执行中,剩余 ${this.taskQueue.length} 个任务`;}
}// 使用调度器
const scheduler = new TaskScheduler();function runTaskWithScheduler(tasks) {document.getElementById('status').textContent = '开始 MessageChannel 调度';tasks.forEach((task) => {scheduler.scheduleTask(() => {task();scheduler.updateStatus();});});
}function runOptimizedTask3() {const tasks = Array.from({ length: 100 }, (_, i) => createSlowTask(i + 1));runTaskWithScheduler(tasks);
}

React 18 的并发渲染机制

React 18 内部使用了类似的时间切片机制,结合优先级调度(Lane 模型)来实现并发渲染:

// React 风格的优先级调度演示
class ReactLikeScheduler {constructor() {this.taskQueue = [];this.isScheduled = false;this.currentPriority = 'normal';// 优先级定义this.priorities = {immediate: 1,normal: 5,low: 10,};this.setupMessageChannel();}setupMessageChannel() {this.channel = new MessageChannel();this.port = this.channel.port2;this.channel.port1.onmessage = () => {this.flushWork();};}scheduleCallback(priority, callback) {const newTask = {callback,priority: this.priorities[priority] || this.priorities.normal,id: Math.random(),};// 插入任务队列并排序this.taskQueue.push(newTask);this.taskQueue.sort((a, b) => a.priority - b.priority);if (!this.isScheduled) {this.isScheduled = true;this.port.postMessage(null);}}flushWork() {const start = performance.now();while (this.taskQueue.length > 0 && performance.now() - start < 5) {const task = this.taskQueue.shift();task.callback();}if (this.taskQueue.length > 0) {this.port.postMessage(null);} else {this.isScheduled = false;console.log('所有任务完成');}}
}// 使用示例
const reactScheduler = new ReactLikeScheduler();// 调度不同优先级的任务
reactScheduler.scheduleCallback('immediate', () => {console.log('高优先级任务执行');
});reactScheduler.scheduleCallback('normal', () => {console.log('普通优先级任务执行');
});reactScheduler.scheduleCallback('low', () => {console.log('低优先级任务执行');
});

完整的演示页面

<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>React 18 渲染优化演示</title><style>body {font-family: Arial, sans-serif;max-width: 1000px;margin: 0 auto;padding: 20px;line-height: 1.6;}.demo-section {margin: 30px 0;padding: 20px;border: 1px solid #ddd;border-radius: 8px;}.animation-container {width: 100%;height: 60px;border: 2px solid #007bff;position: relative;margin: 20px 0;background: linear-gradient(90deg, #f8f9fa 0%, #e9ecef 100%);}.moving-ball {width: 30px;height: 30px;background: linear-gradient(45deg, #007bff, #0056b3);border-radius: 50%;position: absolute;top: 15px;animation: moveBall 4s ease-in-out infinite;box-shadow: 0 2px 8px rgba(0, 123, 255, 0.3);}@keyframes moveBall {0%,100% {left: 10px;}50% {left: calc(100% - 40px);}}.controls {display: flex;flex-wrap: wrap;gap: 10px;margin: 20px 0;}button {padding: 12px 24px;font-size: 14px;cursor: pointer;border: none;border-radius: 6px;background: #007bff;color: white;transition: all 0.2s;}button:hover {background: #0056b3;transform: translateY(-1px);}.status {margin: 15px 0;padding: 15px;background: #f8f9fa;border-left: 4px solid #007bff;border-radius: 4px;font-family: monospace;}.performance-info {display: grid;grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));gap: 15px;margin: 20px 0;}.metric {padding: 15px;background: #e9ecef;border-radius: 6px;text-align: center;}.metric-value {font-size: 24px;font-weight: bold;color: #007bff;}</style></head><body><h1>React 18 渲染优化技术演示</h1><div class="demo-section"><h2>动画性能监测</h2><p>观察小球动画的流畅度,点击不同按钮体验各种优化方案的效果。</p><div class="animation-container"><div class="moving-ball"></div></div><div class="status" id="status">准备就绪 - 选择一个任务执行方案</div><div class="performance-info"><div class="metric"><div class="metric-value" id="completedTasks">0</div><div>已完成任务</div></div><div class="metric"><div class="metric-value" id="executionTime">0</div><div>执行时间 (ms)</div></div><div class="metric"><div class="metric-value" id="fps">60</div><div>当前 FPS</div></div></div><div class="controls"><button onclick="runBlockingTask()">阻塞式执行</button><button onclick="runOptimizedTask1()">requestIdleCallback</button><button onclick="runOptimizedTask2()">requestAnimationFrame</button><button onclick="runOptimizedTask3()">MessageChannel 时间切片</button><button onclick="clearMetrics()">重置计数器</button></div></div><script>// 性能监测let completedTaskCount = 0;let lastFrameTime = performance.now();let frameCount = 0;// FPS 监测function updateFPS() {frameCount++;const now = performance.now();if (now - lastFrameTime >= 1000) {document.getElementById('fps').textContent = frameCount;frameCount = 0;lastFrameTime = now;}requestAnimationFrame(updateFPS);}updateFPS();function updateMetrics() {document.getElementById('completedTasks').textContent = completedTaskCount;}function clearMetrics() {completedTaskCount = 0;updateMetrics();document.getElementById('executionTime').textContent = '0';document.getElementById('status').textContent = '计数器已重置';}// 创建耗时任务function createSlowTask(id) {return function () {const start = performance.now();while (performance.now() - start < 2) {// 模拟计算密集型任务}completedTaskCount++;updateMetrics();};}// 方案1:阻塞式执行function runBlockingTask() {document.getElementById('status').textContent = '⚠️ 执行阻塞任务中... (观察动画卡顿)';const tasks = Array.from({ length: 150 }, (_, i) => createSlowTask(i + 1));setTimeout(() => {const start = performance.now();while (tasks.length > 0) {const task = tasks.shift();task();}const end = performance.now();document.getElementById('executionTime').textContent = (end - start).toFixed(2);document.getElementById('status').textContent = `✅ 阻塞任务完成,总耗时: ${(end - start).toFixed(2)}ms (注意动画卡顿)`;}, 100);}// 方案2:requestIdleCallbackfunction runTaskWithIdleCallback(tasks, startTime) {if (tasks.length === 0) {const totalTime = performance.now() - startTime;document.getElementById('executionTime').textContent = totalTime.toFixed(2);document.getElementById('status').textContent = `✅ requestIdleCallback 任务完成,总耗时: ${totalTime.toFixed(2)}ms`;return;}requestIdleCallback((deadline) => {document.getElementById('status').textContent = `🔄 requestIdleCallback 执行中,剩余 ${tasks.length} 个任务`;while (deadline.timeRemaining() > 1 && tasks.length > 0) {const task = tasks.shift();task();}runTaskWithIdleCallback(tasks, startTime);});}function runOptimizedTask1() {const tasks = Array.from({ length: 150 }, (_, i) => createSlowTask(i + 1));const startTime = performance.now();runTaskWithIdleCallback(tasks, startTime);}// 方案3:requestAnimationFramefunction runTaskWithRAF(tasks, startTime) {if (tasks.length === 0) {const totalTime = performance.now() - startTime;document.getElementById('executionTime').textContent = totalTime.toFixed(2);document.getElementById('status').textContent = `✅ requestAnimationFrame 任务完成,总耗时: ${totalTime.toFixed(2)}ms`;return;}requestAnimationFrame(() => {const frameStart = performance.now();const frameTime = 14; // 留出时间给渲染document.getElementById('status').textContent = `🔄 requestAnimationFrame 执行中,剩余 ${tasks.length} 个任务`;while (performance.now() - frameStart < frameTime && tasks.length > 0) {const task = tasks.shift();task();}runTaskWithRAF(tasks, startTime);});}function runOptimizedTask2() {const tasks = Array.from({ length: 150 }, (_, i) => createSlowTask(i + 1));const startTime = performance.now();runTaskWithRAF(tasks, startTime);}// 方案4:MessageChannel 时间切片class TaskScheduler {constructor() {this.taskQueue = [];this.isScheduled = false;this.frameInterval = 4;this.startTime = 0;this.taskStartTime = 0;this.channel = new MessageChannel();this.port = this.channel.port2;this.channel.port1.onmessage = this.performWork.bind(this);}scheduleTask(callback) {this.taskQueue.push(callback);if (!this.isScheduled) {this.isScheduled = true;this.requestHostCallback();}}requestHostCallback() {this.port.postMessage(null);}performWork() {this.startTime = performance.now();let hasMoreWork = this.workLoop();if (hasMoreWork) {this.requestHostCallback();} else {this.isScheduled = false;const totalTime = performance.now() - this.taskStartTime;document.getElementById('executionTime').textContent = totalTime.toFixed(2);document.getElementById('status').textContent = `✅ MessageChannel 任务完成,总耗时: ${totalTime.toFixed(2)}ms`;}}workLoop() {while (this.taskQueue.length > 0) {if (this.shouldYield()) {document.getElementById('status').textContent = `🔄 MessageChannel 执行中,剩余 ${this.taskQueue.length} 个任务`;return true;}const task = this.taskQueue.shift();task();}return false;}shouldYield() {return performance.now() - this.startTime >= this.frameInterval;}runTasks(tasks) {this.taskStartTime = performance.now();tasks.forEach((task) => this.scheduleTask(task));}}const scheduler = new TaskScheduler();function runOptimizedTask3() {const tasks = Array.from({ length: 150 }, (_, i) => createSlowTask(i + 1));scheduler.runTasks(tasks);}</script></body>
</html>

性能对比分析

方案优点缺点适用场景
阻塞执行实现简单,执行效率高严重阻塞 UI,用户体验差仅适用于少量任务
requestIdleCallback充分利用空闲时间,不影响关键渲染Safari 不支持,执行时机不确定后台数据处理,非关键更新
requestAnimationFrame与浏览器刷新率同步,兼容性好需要手动控制执行时间动画相关任务,视觉更新
MessageChannel可预测的执行时机,类似 React 实现实现复杂度高复杂应用的任务调度

总结

React 18 的并发渲染机制通过时间切片和优先级调度,有效解决了大量计算任务导致的浏览器卡顿问题。主要原理包括:

  1. 时间切片:将连续的渲染过程分割成多个小的时间片段
  2. 优先级调度:使用 Lane 模型为不同任务分配优先级
  3. 可中断渲染:允许高优先级任务中断低优先级任务的执行

在实际开发中,我们可以根据具体场景选择合适的优化方案:

  • 对于动画和视觉更新,优先考虑 requestAnimationFrame
  • 对于后台数据处理,可以使用 requestIdleCallback(注意兼容性)
  • 对于复杂的任务调度,可以参考 React 的 MessageChannel 实现

通过合理运用这些技术,我们能够构建出既高性能又用户体验友好的 Web 应用。

http://www.xdnf.cn/news/979885.html

相关文章:

  • AX620Q上模型部署流程
  • Spring Security是如何完成身份认证的?
  • BUG调试案例十四:TL431/TL432电路发热问题案例
  • Python训练营打卡DAY51
  • 机器学习核心概念速览
  • 基于ElasticSearch的法律法规检索系统架构实践
  • livetalking实时数字人多并发
  • uni-app项目实战笔记1--创建项目和实现首页轮播图功能
  • 告别excel:AI 驱动的数据分析指南
  • elementui使用Layout布局-对齐方式
  • input+disabled/readonly问题
  • Vue3 + TypeScript + Element Plus 表格行按钮不触发 row-click 事件、不触发勾选行,只执行按钮的 click 事件
  • Explore Image Deblurring via Encoded Blur Kernel Space论文阅读
  • 时序数据库IoTDB数据模型建模实例详解
  • Jmeter中变量如何使用?
  • MySQL 三表 JOIN 执行机制深度解析
  • 基础数论一一同余定理
  • Qt 动态插件系统QMetaObject::invokeMethod
  • 【docker】docker registry搭建私有镜像仓库
  • 开源 java android app 开发(十二)封库.aar
  • SD-WAN 技术如何助力工业物联网(IIoT)数据传输?深度解析传统方案对比与应用实践
  • Chrome 优质插件计划
  • 智慧农业物联网实训中心建设方案
  • 趋境科技英特尔生态沙龙举办,打通大模型私有化“最后一公里”
  • 当简约美学融入小程序 UI 设计:开启高效交互新篇
  • 【Java学习日记38】:C语言 fabs 与 Java abs 绝对值函数
  • element plus的el-form重置无效
  • CavityPlus: 北大团队研发的综合性蛋白质结合位点检测及功能分析网络服务器
  • 【python】预测投保人医疗费用,附insurance.csv数据集
  • 嵌入式系统内核镜像相关(三)