createElement 执行过程

React createElement 函数的执行流程和实现原理

问题

React 中 createElement 函数是如何执行的?JSX 是如何转换成虚拟 DOM 的?

解答

JSX 到 createElement

JSX 代码会被 Babel 编译成 createElement 调用:

// JSX 写法
const element = <div className="box">Hello</div>;

// 编译后
const element = React.createElement('div', { className: 'box' }, 'Hello');

createElement 函数签名

createElement(type, config, ...children)
  • type: 元素类型,可以是字符串(如 ‘div’)或组件
  • config: 属性配置对象
  • children: 子元素

执行过程

function createElement(type, config, ...children) {
  // 1. 初始化 props 对象
  const props = {};
  
  // 2. 提取保留属性
  let key = null;
  let ref = null;
  
  if (config != null) {
    // 提取 key
    if (config.key !== undefined) {
      key = '' + config.key;
    }
    
    // 提取 ref
    if (config.ref !== undefined) {
      ref = config.ref;
    }
    
    // 3. 复制其他属性到 props
    for (const propName in config) {
      if (
        Object.hasOwnProperty.call(config, propName) &&
        propName !== 'key' &&
        propName !== 'ref'
      ) {
        props[propName] = config[propName];
      }
    }
  }
  
  // 4. 处理 children
  if (children.length === 1) {
    props.children = children[0];
  } else if (children.length > 1) {
    props.children = children;
  }
  
  // 5. 处理 defaultProps
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (const propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  
  // 6. 返回 ReactElement 对象
  return {
    $$typeof: Symbol.for('react.element'), // 标识这是 React 元素
    type,
    key,
    ref,
    props,
  };
}

完整示例

// 嵌套 JSX
const app = (
  <div id="app">
    <h1>Title</h1>
    <p>Content</p>
  </div>
);

// 编译结果
const app = React.createElement(
  'div',
  { id: 'app' },
  React.createElement('h1', null, 'Title'),
  React.createElement('p', null, 'Content')
);

// 最终生成的对象结构
{
  $$typeof: Symbol(react.element),
  type: 'div',
  key: null,
  ref: null,
  props: {
    id: 'app',
    children: [
      { $$typeof: Symbol(react.element), type: 'h1', props: { children: 'Title' } },
      { $$typeof: Symbol(react.element), type: 'p', props: { children: 'Content' } }
    ]
  }
}

关键点

  • keyref 是保留属性,不会出现在 props
  • children 会被放入 props.children,单个子元素不是数组
  • $$typeof 用于标识 React 元素,防止 XSS 攻击(JSON 无法包含 Symbol)
  • defaultProps 只在属性值为 undefined 时生效
  • createElement 只创建描述对象,不涉及真实 DOM 操作