React并发模式与Suspense原理

一、核心知识点梳理

  1. 并发更新(Concurrent Updates)
  • 核心概念:传统 React 更新是 "同步且不可中断" 的,一旦开始渲染就会占用主线程直到完成,容易导致页面卡顿;而并发更新允许 React 中断、暂停、恢复或放弃正在进行的渲染,优先处理高优先级任务(比如用户输入),低优先级任务(比如列表渲染)可以被延后,这是 Concurrent Mode 的核心。
  • 关键逻辑:React 通过 "优先级调度" 实现并发,给不同的更新任务分配不同的优先级(比如 ImmediatePriority、UserBlockingPriority、NormalPriority 等),调度器(Scheduler)会根据优先级决定先执行哪个任务,低优先级任务会被高优先级任务 "打断",等主线程空闲后再恢复。
  1. Suspense 工作原理
  • 核心概念:Suspense 允许组件 "暂停渲染",直到某个异步操作(比如数据请求、代码加载)完成,期间显示 fallback 占位内容,异步操作完成后再渲染实际内容。
  • 工作流程
  1. 组件渲染时如果读取了一个 "未就绪" 的异步资源(比如封装了 Promise 的对象),会抛出这个 Promise;

  2. React 捕获到这个 Promise 后,会暂停当前组件的渲染,显示 Suspense 定义的 fallback;

  3. 当 Promise 成功 resolve 后,React 会重新渲染该组件,此时异步资源已就绪,渲染实际内容;

    如果 Promise reject,会触发错误边界。

  4. 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 秒后异步资源就绪,重新渲染实际内容;

三、要点总结

  1. 并发模式核心价值
  • 解决 "长任务阻塞主线程" 问题,提升用户交互的响应性;
  • 核心是 "优先级调度",而非 "多线程"(React 仍运行在单线程)。
  1. Suspense 核心
  • 本质是 "渲染时的异常捕获(Promise)+ 异步重试";
  • 不仅能用于数据请求,还能用于代码分割(React.lazy + Suspense)。
  1. useDeferredValue/useTransition 区别
  • useTransition:主动标记 "低优先级更新",控制更新行为;
  • useDeferredValue:被动创建 "延迟值",控制值的更新时机;
    两者都是基于并发更新的优先级调度实现,最终目的都是不阻塞高优先级任务。
相关推荐
费曼学习法3 小时前
React 18 并发模式(Concurrent Mode):Fiber 架构的终极进化
javascript·react.js
gogoing6 小时前
React 分包加载优化
前端·react.js
openKaka_8 小时前
beginWork 的第一站:HostRoot 如何把 App 接入 Fiber 树
前端·javascript·react.js
孟祥_成都9 小时前
前端唯一的护城河?结合 AI 将字节组件库 Headless 化后的感想~
前端·人工智能·react.js
不爱学英文的码字机器11 小时前
被 AE 的关键帧折磨过的人,应该试试这个用 React 写视频的路子
前端·react.js·音视频
不会写DN11 小时前
为什么需要 @types/react? 解决“无法找到模块 react 的声明文件”报错
前端·react.js·前端框架
前端初见11 小时前
React 开发实战全攻略:从基础到项目实战(面向 Vue 开发者)
javascript·vue.js·react.js
右耳朵猫AI12 小时前
React技术周刊 2026年第14周
前端·react.js·前端框架
csj5012 小时前
前端基础之《React(8)—webpack简介-其他配置》
前端·react.js