React源码解析(一)—— JSX

287 阅读3分钟

在React中我们使用JSX去描述UI

如下所示:

import React from 'react';

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

从JSX到JS的转换

对于浏览器而言,它只会理解javascript代码所以需要对代码进行转换,将JSX转变为JS代码: 在React17之前是由React包内部的createElement函数去实现的;

import React from 'react';

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

而在此之后这个转换过程在编译的时候会使用Babel进行处理。

import { jsx } from 'react/jsx-runtime';

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

当我们使用createElement去处理节点时得到的是一个React元素,其数据类型被定义为ReactElement,其结构如下:

    const element = {
        $$typeof: REACT_ELEMENT_TYPE,   // $$typeof:Symbol类型的值用于标记该元素为ReactElement类型
        type, // 元素类型
        key, // 唯一标识
        ref,  // 实例
        props, // 属性值
    };

为了方便处理React元素, 使用一个函数去构造ReactElement

function ReactElement(
   type,
   key,
   ref,
   props,
 ) {
 
   const element = {
   $$typeof: REACT_ELEMENT_TYPE,
   type,
   key,
   ref,
   props,
   };
 
   return element;
 }

createElement

createElement内部我们需要做的就是将如下调用,转换为React元素

React.createElement('div', null, 'Hello world!')

以下是一个简化版的实现:

function hasValidKey(config){
   return config.key !== undefined;
}

function hasValidRef(config){
   return config.ref !== undefined;
}


export function createElement  (type, config, children)  {

   let propName;
   const props = {};

   let key = null;
   let ref = null;

   if (config != null) {
       
       if (hasValidRef(config)) {
           ref = config.key;
       }
       if (hasValidKey(config)) {
           key = '' + config.key;
       }

       // 将除key和ref之外的值放入props中
       for (propName in config) {
           if (
               hasOwnProperty.call(config, propName) &&
               propName !== 'key' && propName !== 'ref' 
           ) {         
               props[propName] = config[propName];
           }
       }
   }

   const childrenLength = arguments.length - 2;

   // 将子节点添加到props的children属性中
   if (childrenLength === 1) {
       props.children = children;
   } else if (childrenLength > 1) {
       const childArray = Array(childrenLength);
       
       for (let i = 0; i < childrenLength; i++) {
           childArray[i] = arguments[i + 2];
       }
           props.children = childArray;
   }

   // 处理defaultProps
   if (type && type.defaultProps) {
       const defaultProps = type.defaultProps;
       for (propName in defaultProps) {
           if (props[propName] === undefined) {
               props[propName] = defaultProps[propName];
           }
       }
   }

   // 返回处理后的节点
   return ReactElement(
       type,
       key,
       ref,
       props,
   );
}


函数的主要处理流程包括:

  1. 接收参数:函数接收三个参数,type 表示元素的类型,config 是一个对象,包含元素的属性(props)、键(key)和引用(ref),children 是元素的子元素。
  2. 处理属性:函数会从 config 对象中提取出 keyref,并将其分别存储在独立的变量中。然后,它会将 config 对象中剩余的属性复制到一个新的 props 对象中。
  3. 处理子元素:函数会检查传递给 createElement 的子元素数量。如果有多个子元素,它们会被放入一个数组中,然后作为 props.children 属性添加到 props 对象中。
  4. 应用默认属性:如果 type 对象定义了 defaultProps,函数会将这些默认属性应用到 props 对象中,填补任何未在 config 中显式定义的属性。
  5. 创建并返回React元素:最后,函数会使用 typekeyrefprops 对象来创建一个React元素,并将其返回。这个元素可以被React DOM或其他渲染器用来在页面上渲染实际的DOM元素。

总结

JSX到JS的转换过程:

  1. JSX以声明式的方式编写React组件。
  2. 通过createElementjsx将JSX调用转换为React元素,包括提取属性、处理子元素、应用默认属性,并最终返回一个React元素对象。