以源码角度理解jsx

16 阅读3分钟

JSX 是 JavaScript 的一种语法扩展,全称叫做( JavaScript XML),允许你在 JavaScript 代码中编写类似 HTML 的结构。它本质上是 React.createElement(component, props, ...children)调用的语法糖,最终会被编译成用于描述 UI 的 JavaScript 对象,而这个用于描述 UI 的 JavaScript 对象便是我们所提起的虚拟DOM。

源码解析

// 编写的JSX
const element = <div className="container">Hello</div>;

// 实际的 JavaScript,调用 React.createElement函数
const element = React.createElement(
  'div',
  { className: 'container' },
  'Hello'
);

// React.createElement 返回的对象
{
  $$typeof: Symbol(react.element),
  type: 'div',  //标签类型
  key: null,    //key,列表渲染优化, 这也是为什么react的jsx有key这个属性,而原生html标签却没有
  ref: null, // dom引用
  props: {
    className: 'container', //样式
    children: 'Hello'  //innerHTML的信息
  },
  _owner: null,
  _store: {}
}

JSX 编译过程

JSX 代码 → Babel/Parser → AST → transform → JavaScript 代码

JSX编译与虚拟DOM创建(render)

JSX到虚拟DOM的完整流程

JSX代码 → Babel解析 → AST抽象语法树 → JSX转换 → React.createElement调用 → 虚拟DOM对象

Babel转换的两种模式

// (React 16及之前)
// JSX: <div>Hello</div>
// 转换为:
React.createElement('div', null, 'Hello')

// (React 17+)
// JSX: <div>Hello</div>
// 转换为:
import { jsx as _jsx } from 'react/jsx-runtime'
_jsx('div', { children: 'Hello' })

完整的虚拟DOM对象结构

// React.createElement返回的完整虚拟DOM对象
const vdom = {
  // 唯一标识React元素类型
  $$typeof: Symbol.for('react.element'),
  
  // 元素类型
  type: 'div', // 可以是字符串、函数组件、类组件、Fragment等
  
  // React内部使用的唯一标识
  _owner: null,
  _store: {},
  _self: null,
  _source: null,
  
  // 关键属性
  key: null,      // 列表渲染优化
  ref: null,      // DOM引用
  
  // 所有属性(包括children)
  props: {
    className: 'container',
    style: { color: 'red' },
    children: [
      'Hello',
      { $$typeof: Symbol.for('react.element'), type: 'span', ... }
    ]
  }
}

虚拟DOM到真实DOM的渲染流程(commit)

完整渲染流程图

虚拟DOM树 → ReactDOM.render() → 
├── 协调阶段 (Render)
│   ├── 创建Fiber树
│   ├── 构建工作单元
│   └── 执行Diff算法
│
├── 提交阶段 (Commit)
│   ├── 更新前准备
│   ├── DOM操作执行
│   └── 生命周期调用
│
└── 真实DOM更新

详细渲染步骤

步骤1:初始化渲染

// 1. ReactDOM.render调用
ReactDOM.render(
  <App />,           // JSX元素(虚拟DOM)
  document.getElementById('root')  // 挂载点
);

// 2. 创建Fiber根节点
function createFiberRoot(containerInfo) {
  return {
    current: null,        // 当前渲染的Fiber树
    containerInfo: containerInfo, // DOM容器
    pendingChildren: null,
    // ... 其他内部属性
  };
}

// 3. 开始渲染流程
function scheduleUpdateOnFiber(fiber, lane) {
  // 调度更新,进入React调度系统
  ensureRootIsScheduled(root);
}

步骤2:协调阶段(Reconciliation)

// Fiber节点结构
const fiberNode = {
  tag: WorkTag,           // 组件类型(函数组件、类组件、宿主组件等)
  key: null,              // 唯一标识
  elementType: Function,  // 组件函数/类
  type: 'div',           // DOM标签名
  stateNode: null,        // 对应的真实DOM节点
  return: parentFiber,    // 父Fiber
  child: childFiber,      // 第一个子Fiber
  sibling: nextFiber,     // 下一个兄弟Fiber
  pendingProps: props,    // 新属性
  memoizedProps: props,   // 上次渲染的属性
  memoizedState: state,   // 状态
  updateQueue: null,      // 更新队列
  // ... 其他Fiber属性
};

// 构建Fiber树的入口函数
function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

// 处理单个Fiber节点
function performUnitOfWork(unitOfWork) {
  const current = unitOfWork.alternate;
  
  // beginWork阶段:创建子Fiber节点
  let next = beginWork(current, unitOfWork, renderLanes);
  
  if (next === null) {
    // completeWork阶段:完成当前节点
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }
}

// beginWork:根据组件类型执行不同逻辑
function beginWork(current, workInProgress, renderLanes) {
  switch (workInProgress.tag) {
    case FunctionComponent:
      return updateFunctionComponent(
        current, workInProgress, workInProgress.type, renderLanes
      );
    case ClassComponent:
      return updateClassComponent(
        current, workInProgress, workInProgress.type, renderLanes
      );
    case HostComponent: // DOM元素
      return updateHostComponent(current, workInProgress, renderLanes);
    case HostText: // 文本节点
      return updateHostText(current, workInProgress);
    // ... 其他类型
  }
}

// Diff算法核心(reconcileChildren)
function reconcileChildren(current, workInProgress, nextChildren) {
  if (current === null) {
    // 首次渲染:直接插入
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderLanes
    );
  } else {
    // 更新:Diff比较
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderLanes
    );
  }
}

// Diff算法策略
function reconcileSingleElement(returnFiber, currentFirstChild, element) {
  const key = element.key;
  let child = currentFirstChild;
  
  while (child !== null) {
    // 1. 比较key是否相同
    if (child.key === key) {
      // 2. 比较type是否相同
      if (child.elementType === element.type) {
        // 复用节点
        deleteRemainingChildren(returnFiber, child.sibling);
        const existing = useFiber(child, element.props);
        existing.return = returnFiber;
        return existing;
      }
      // key相同但type不同,删除旧节点
      deleteRemainingChildren(returnFiber, child);
      break;
    }
    child = child.sibling;
  }
  
  // 创建新节点
  const created = createFiberFromElement(element);
  created.return = returnFiber;
  return created;
}

步骤3:提交阶段(Commit)

// 提交阶段入口
function commitRoot(root) {
  const finishedWork = root.finishedWork;
  
  // 阶段1:DOM更新前(调用getSnapshotBeforeUpdate)
  commitBeforeMutationEffects();
  
  // 阶段2:DOM更新
  commitMutationEffects(root, renderPriorityLevel);
  
  // 切换当前Fiber树
  root.current = finishedWork;
  
  // 阶段3:DOM更新后
  commitLayoutEffects(finishedWork, root, lanes);
}

// DOM创建函数
function commitPlacement(finishedWork) {
  const parentFiber = getHostParentFiber(finishedWork);
  const parent = parentFiber.stateNode;
  
  // 获取插入位置
  const before = getHostSibling(finishedWork);
  
  if (parent.tag === HostComponent) {
    // 插入DOM节点
    insertOrAppendPlacementNode(finishedWork, before, parent);
  }
}

// 具体的DOM操作方法
function createInstance(type, props, rootContainerInstance) {
  const domElement = document.createElement(type);
  
  // 设置属性
  updateProperties(domElement, type, {}, props);
  
  // 设置初始属性值
  setInitialProperties(domElement, type, props);
  
  return domElement;
}

// 属性更新
function updateProperties(domElement, tag, lastProps, nextProps) {
  for (let propKey in nextProps) {
    const nextProp = nextProps[propKey];
    const lastProp = lastProps[propKey];
    
    if (propKey === 'children') {
      if (typeof nextProp === 'string' || typeof nextProp === 'number') {
        setTextContent(domElement, nextProp);
      }
    } else if (propKey === 'style') {
      setValueForStyles(domElement, nextProp, lastProp);
    } else if (isCustomProp(propKey)) {
      // 自定义属性处理
    } else if (propKey === 'dangerouslySetInnerHTML') {
      // 危险HTML设置
    } else if (shouldIgnoreAttribute(propKey)) {
      // 忽略的属性
    } else if (isEventProp(propKey)) {
      // 事件处理
      updateEventListener(domElement, propKey, lastProp, nextProp);
    } else {
      // 普通属性
      setValueForProperty(domElement, propKey, nextProp, tag);
    }
  }
}

步骤4:生命周期与副作用调用

// 类组件生命周期调用
function commitLifeCycles(finishedRoot, current, finishedWork) {
  switch (finishedWork.tag) {
    case ClassComponent:
      const instance = finishedWork.stateNode;
      if (current === null) {
        // 首次挂载
        instance.componentDidMount();
      } else {
        const prevProps = current.memoizedProps;
        const prevState = current.memoizedState;
        // 更新
        instance.componentDidUpdate(prevProps, prevState);
      }
      break;
      
    case HostRoot:
      // 根组件更新完成
      flushPassiveEffects();
      break;
      
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent:
      // 函数组件的副作用
      commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
      break;
  }
}

// Hooks副作用处理
function commitHookEffectListMount(tag, finishedWork) {
  const updateQueue = finishedWork.updateQueue;
  const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  
  if (lastEffect !== null) {
    const firstEffect = lastEffect.next;
    let effect = firstEffect;
    
    do {
      if ((effect.tag & tag) === tag) {
        // 执行useEffect回调
        const create = effect.create;
        const destroy = create();
        effect.destroy = destroy;
      }
      effect = effect.next;
    } while (effect !== firstEffect);
  }
}