【前端架构和框架】react准备知识

本篇我们来了解下react中用到的一些基础知识和概念,作为后续我们学习react三大模块的时候的前置知识储备。

react源码目录解析

react项目的源码是用monorepo的工程化架构组织的,如下核心代码都在packages包下面,分了很多个子包

bash 复制代码
根目录
├── fixtures        # 包含一些给贡献者准备的小型 React 测试项目
├── packages        # 包含元数据(比如 package.json)和 React 仓库中所有 package 的源码(子目录 src)
├── scripts         # 各种工具链的脚本,比如git、jest、eslint等

packages 目录

下面来看下包含核心源码的 packages 目录:

java 复制代码
packages/
├── react/
├── react-dom/
├── react-reconciler/
├── scheduler/
├── shared/
├── react-server/
├── react-native-renderer/
├── react-devtools/
└── ...
  • react/
    提供核心API如createElementmemoContext和Hooks实现,所有通过import React from 'react'引入的API均在此定义。
  • scheduler/
    任务调度器,负责优先级排序和时间切片,确保高优先级任务(如用户交互)优先执行。
  • react-reconciler/
    协调器实现,包含Fiber架构的diff算法和副作用标记逻辑。
  • shared/
    公共工具方法和常量定义,被多个模块复用。
  • react-dom/
    浏览器环境渲染器,处理DOM操作和事件系统,是ReactDOM.render()的底层实现。

上面几个包是比较核心的包,react所有的流程都在这些包里面实现。调度层Scheduler (状态更新触发调度器分配任务优先级) → 协调层Reconciler (生成Fiber副作用链表) → 渲染层Renderer (提交变更到界面)。如果想调试源码建议从packages/react-dom/src/client/ReactDOMRoot.js入口文件切入,逐步分析渲染链路。

jsx和babel

react使用jsx来表示UI声明式,react/jsx-runtime是在React 17版本中引入的新JSX转换方式。在此之前(React 16及更早版本),JSX会通过Babel转换为React.createElement调用,而从React 17开始,官方推荐使用react/jsx-runtime提供的jsx函数进行转换。这一变更使得开发者无需在每个文件中显式引入React即可使用JSX语法。本质上没有变化,都是把jsx编译成渲染函数,y运行时渲染函数去生成虚拟DOM。

Babel 的作用
  • let/const 转换为 var
  • 将箭头函数 () => {} 转换为 function(){}
  • class 类语法转换为构造函数
  • 支持 JSX 转换为 React.createElement()_jsx
  • 支持异步语法 async/await 转换为 Promise
js 复制代码
import React from 'react'; // 手动引入 react

function App() {
    return <h1>Hello World</h1>;
}

// 转换结果
// React 17之前,JSX 转换结果
import React from 'react';

function App() {
    return React.createElement('div', null, 'Hello world!');
}

// React 17之后,JSX 转换结果
import { jsx as _jsx } from 'react/jsx-runtime'; // 由编译器引入(禁止自己引入!)

function App() {
    return _jsx('div', { children: 'Hello world!' });
}

JSX 转换的过程大致分为两步:

  • 编译时:由 Babel 编译实现,Babel 会将 JSX 语法转换为标准的 JavaScript API;
  • 运行时:由 React 实现,jsx 方法 和 React.createElement 方法;

上面对于渲染函数的引用分为了两种方式

  1. _jsx 解构引用
  2. React.createElement 全量引用

直接引用 React 会将非 createElement 相关的 API (比如上面例子中就没有用到 useState)一并被打包进编译文件中,而使用解构,可以通过 tree-sharking 来排除掉项目中未使用到的 React API,这就是为什么 JSX runtime 可以减小打包体积

看到这里你应该明白了,其实 _jsxcreateElement本质上相同,都是一个创建虚拟DOM的函数。我们看下react内部源码实现

js 复制代码
// packages/react/src/jsx.ts
import { REACT_ELEMENT_TYPE } from 'shared/ReactSymbols';
import {
    Type,
    Ref,
    Key,
    Props,
    ReactElementType,
    ElementType
} from 'shared/ReactTypes';

const ReactElement = function (
    type: Type,
    key: Key,
    ref: Ref,
    props: Props
): ReactElementType {
    const element = {
        $$typeof: REACT_ELEMENT_TYPE,
        type,
        key,
        ref,
        props,
        __mark: 'erxiao'
    };
    return element;
};

// packages/react/src/jsx.ts
// ...之前的代码

export const jsx = (type: ElementType, config: any, ...children: any) => {
    let key: Key = null;
    let ref: Ref = null;
    const props: Props = {};
    for (const prop in config) {
        const val = config[prop];
        if (prop === 'key') {
            if (val !== undefined) {
                key = '' + val;
            }
            continue;
        }
        if (prop === 'ref') {
            if (val !== undefined) {
                ref = val;
            }
            continue;
        }
        if ({}.hasOwnProperty.call(config, prop)) {
            props[prop] = val;
        }
    }
    const childrenLength = children.length;
    if (childrenLength) {
        if (childrenLength === 1) {
            props.children = children[0];
        } else {
            props.children = children;
        }
    }
    return ReactElement(type, key, ref, props);
};

// packages/react/index.ts
import { jsx } from './src/jsx';
export default {
    version: '1.0.0',
    createElement: jsx
};

fiber数据结构

  1. Fiber 节点结构:每个节点包含类型、属性、DOM 节点引用以及形成链表的指针(parent、child、sibling)

  2. 双缓存机制:通过 alternate 属性实现当前树与工作树的切换

  3. 工作循环

    • 调度阶段:决定下一个要执行的工作单元
    • 执行阶段:处理每个工作单元,创建 / 更新 Fiber 节点
    • 提交阶段:将变更应用到实际 DOM
  4. 增量渲染:利用时间切片5ms时间执行工作,超时则让出主线程

  5. 优先级调度:虽然简化版没有完整实现优先级,但通过 timeRemaining () 检查实现了基本的可中断性

  6. 可恢复性:通过链表结构记录工作进度,下次可以从 nextUnitOfWork 继续

  7. 双缓存机制:通过 current (当前树) 和 workInProgress (工作树) 实现渲染准备与实际 DOM 更新分离

  8. 两阶段处理

    • 调度阶段 (可中断):计算变更、确定 effect
    • 提交阶段 (不可中断):执行 DOM 操作
ini 复制代码
function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // 作为静态数据结构的属性
  this.tag = tag;
  this.key = key;
  this.elementType = null;
  this.type = null;
  this.stateNode = null;

  // 用于连接其他Fiber节点形成Fiber树
  this.return = null;
  this.child = null;
  this.sibling = null;
  this.index = 0;

  this.ref = null;

  // 作为动态的工作单元的属性
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;

  this.mode = mode;

  this.effectTag = NoEffect;
  this.nextEffect = null;

  this.firstEffect = null;
  this.lastEffect = null;

  // 调度优先级相关
  this.lanes = NoLanes;
  this.childLanes = NoLanes;

  // 指向该fiber在另一次更新时对应的fiber
  this.alternate = null;
}

这种设计使 React 能够在处理复杂 UI 时不会阻塞主线程,保证了良好的用户交互响应性。

微任务和宏任务

react18中用了微任务(Microtasks)和宏任务(Macrotasks)来实现高效任务调度的核心机制,它们的区别主要体现在执行时机、优先级和适用场景上。React 利用这种区别来优化渲染性能,确保用户交互等关键操作优先响应。

  • 微任务queueMicrotaskPromise.then 调度为微任务,确保立即响应,避免用户操作卡顿。
  • 宏任务MessageChannel实现可中断调度为宏任务,不可用时降级为setTimeout ,延迟执行以避免阻塞关键操作。

主要实现以下:

  • 异步处理任务,可以实现异步任务编排,批处理功能;

  • 让出主线程,实现任务分片,把长任务分成多个小任务,在每一帧在去执行;

  • 实现更细粒度的优先级调度,紧急打断非紧急任务。

js 复制代码
// 用于高优先级任务的微任务队列
const microtaskQueue = [];
let isMicrotaskFlushing = false;

// 处理微任务队列,可以批处理
function flushMicrotasks() {
  isMicrotaskFlushing = true;
  try {
    // 执行所有微任务
    while (microtaskQueue.length > 0) {
      const callback = microtaskQueue.shift();
      callback();
    }
  } finally {
    isMicrotaskFlushing = false;
  }
}

  if (typeof queueMicrotask === 'function') {
        queueMicrotask(flushMicrotasks);
      } else {
        // 降级方案:使用 Promise
        Promise.resolve().then(flushMicrotasks).catch(error => {
          console.error('Microtask error:', error);
        });
      }

// 处理宏任务队列(通过 MessageChannel)

// 用于低优先级任务的 MessageChannel
const channel = new MessageChannel();
const port1 = channel.port1;
const port2 = channel.port2;
// 执行回调任务
port1.onmessage = function() {
 ......
};

// 调度宏任务
function scheduleMacrotask() {
  port2.postMessage(null);
}
  
};
    

任务优先级

我们已经知道优先级意味着任务的过期时间。设想一个大型React项目,在某一刻,存在很多不同优先级任务,对应不同的过期时间。

同时,又因为任务可以被延迟,所以我们可以将这些任务按是否被延迟分为:

  • timerQueue:保存未就绪任务
  • taskQueue:保存已就绪任务

每当有新的未就绪的任务被注册,我们将其插入timerQueue并根据开始时间重新排列timerQueue中任务的顺序。

timerQueue中有任务就绪,即startTime <= currentTime,我们将其取出并加入taskQueue

取出taskQueue中最早过期的任务并执行他。

为了能在复杂度找到两个队列中时间最早的那个任务,Scheduler使用小顶堆实现了优先级队列

参考

你真的了解 React18 的并发吗?

React18源码: schedule任务调度messageChannel

React官方网站中文文档

React技术揭秘

相关推荐
渣哥3 小时前
从配置文件到 SpEL 表达式:@Value 在 Spring 中到底能做什么?
javascript·后端·面试
拜无忧3 小时前
【小游戏】逃逸小球h5,登录背景,缺口逃逸小球多边形
前端
烛阴3 小时前
Python 列表推导式:让你的代码更优雅、更高效
前端·python
文心快码BaiduComate3 小时前
开工不累,双强护航:文心快码接入 DeepSeek-V3.2-Exp和 GLM-4.6,助你节后高效Coding
前端·人工智能·后端
快乐是一切4 小时前
PDF底层格式之水印解析与去除机制分析
前端·数据结构
麋鹿原4 小时前
Android Room 数据库之简单上手
前端·kotlin
一小池勺4 小时前
改变上下文的 API:call, apply, bind
前端·javascript
三门4 小时前
vue官网新读之后收获记录
前端
Keepreal4964 小时前
使用Canvas绘制转盘
javascript·vue.js·canvas