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:被动创建 "延迟值",控制值的更新时机;
    两者都是基于并发更新的优先级调度实现,最终目的都是不阻塞高优先级任务。
相关推荐
欧呦固5 小时前
# 🚀 Solidity从零到一:手把手教你开发ERC-20代币
react.js
Ruihong5 小时前
Vue 转 React:揭秘 scoped 样式是如何被 VuReact 编译的?
vue.js·react.js·面试
Ruihong5 小时前
Vue 组件样式 <style> 转 React:VuReact 怎么处理?
vue.js·react.js·面试
A923A6 小时前
Vue 和 React 常用脚手架工具总结
前端·vue.js·react.js·脚手架
Highcharts.js7 小时前
步骤总结|使用 React + Highcharts 实现动态更新图表
前端·javascript·react.js·前端框架·highcharts·图表渲染
好家伙VCC7 小时前
# React发散创新:从状态管理到自定义Hook的极致实践与性能优化在现代前端开发
java·javascript·python·react.js·性能优化
M ? A18 小时前
Vue 动态组件在 React 中,VuReact 会如何实现?
前端·javascript·vue.js·经验分享·react.js·面试·vureact
糯米团子7491 天前
react速通-2
前端·react.js·前端框架
糯米团子7491 天前
react速通-3
javascript·react.js·前端框架