React源码系列(二):实现React.createElement和JSX

1、浏览器是怎么认识JSX的?

javascript 复制代码
function App () {
  return <div>
    hello world
    <span>jsx是什么</span>
  </div>
}

这是一个我们通过JSX语法写的一个App 组件,但是这并不是一个js代码,所以浏览器并不识别这段代码,也就无法在浏览器上渲染出来,所以就需要通过babel把它编译成浏览器识别的js代码

php 复制代码
var _reactJsxRuntime = require("react/jsx-runtime");
function App() {
  return /*#__PURE__*/ _reactJsxRuntime.jsxs("div", {
    children: [
      "hello world",
      /*#__PURE__*/ _reactJsxRuntime.jsx("span", {
        children: "jsx\u662F\u4EC0\u4E48"
      })
    ]
  });
}

由代码可以看出,JSX最终由babel编译成了一个_jsxs()_reactJsxRuntime.jsxs方法的调用,从而将组件代码转化为可以执行的js代码。

2、在react源码中是怎么处理JSX的?

在react源码中,暴露了jsx方法,这个方法接收3个参数,最终返回了一个ReactElement方法的调用

而这个ReactElement方法最终返回了一个element对象

所以在React中,jsx的本质上就是ReactElement方法调用的方式,创建出来一个与之对应的element对象,这个对象包含$$typeof type key ref props等属性。

3、JSX是什么?

JSX就是JavaScript的一种语法扩展,是描述UI的一种方法,最终会以React.createElement()或者是jsx()方法执行的方式展现,返回一个ReactElement对象,在react17版本之后如果不使用其他react API就可以不用引入React,因为react官方和babel官方进行了合作,可以直接通过react/jsx-runtime直接进行转换。

4、实现React.createElement方法

最终返回一个element对象,所以先写ReactElement方法,构造一个element对象,也就是React元素

typescript 复制代码
const supportSymbol = typeof Symbol === 'function' && Symbol.for // 判断当前宿主环境是否支持Symbol

export const REACT_ELEMENT_TYPE = supportSymbol ? Symbol.for('react.element') : 0xeac7


/**
 * ReactElement构造函数
 * @param type
 * @param key 组件的key
 * @param ref 组件的ref
 * @param props 组件的props
 */
const ReactElement = (type, key, ref, props) => {
  const elelment = {
    $$typeof:REACT_ELEMENT_TYPE, // 通过这个属性来指明当前对象是ReactElement结构对象
  	
    type,
    key,
    ref,
    props,

    __mark: 'Apollo', // 用于区分真实React中的ReactElement对象
  }
  return element
}

由源码知道,ReactElement方法是在jsx方法中调用

kotlin 复制代码
/**
 * 
 * @param type 元素类型
 * @param config 元素属性,包括key,不包括子元素children
 * @param maybeChildren 子元素children
 * @returns 返回一个ReactElement
 */
const jsx  =(type, config, ...maybeChildren) => {
  // reactElement 自身的属性
  let key = null
  let ref = null
  // 创建一个空对象props,用于存储属性
  const props = {}
// 遍历config对象,将ref、key这些ReactElement内部使用的属性提取出来,不应该被传递下去
  for(const propName in config){
    const val = config[propName]
    if(propName === 'key'){
      if(val !== undefinde){
      	key = '' + val
      }
      continue
    }
    if(propName === 'ref'){
      if (val !== undefined) {
        ref = val
      }
      continue
    }
    // 如果propName是config自己的属性 则赋值给props
    if({}.hasOwnProperty.call(config, propName)){
      props[propName] = val
    }

    const childrenLength = maybeChildren.length
    if(childrenLength){
      if(childrenLength === 1){
        props.children = maybeChildren[0]
      }else{
        props.children = maybeChildren
      }
    }
  }
  return ReactElement(type, key, ref, props)
}

5、实现JSX方法

React.createElement方法和jsx方法的区别这里只体现在第三个参数上

ini 复制代码
/**
 * 
 * @param type 元素类型
 * @param config 元素属性
 * @param maybeKey 可能的key值
 * @returns 返回一个ReactElement
 */
const jsx = (type: ElementType, config: any, maybeKey: any) => {
  // 初始化key和ref为空
  let key = null;
  let ref = null;

  // 创建一个空对象props,用于存储属性
  const props: Props = {};

  // 遍历config对象,将ref、key这些ReactElement内部使用的属性提取出来,不应该被传递下去
  for (const prop in config) {
    const val = config[prop];
    if (prop === "key") {
      continue;
    }
    if (prop === "ref") {
      if (val !== undefined) {
        ref = val;
      }
      continue;
    }
    // 一般使用{...props}将所有属性都传递下去,所以摘除ref、key属性外需要被保存到props中
    if ({}.hasOwnProperty.call(config, prop)) {
      props[prop] = val;
    }
  }

  // 将 maybeKey 添加到 key 中
  if (maybeKey !== undefined) {
    key = "" + maybeKey;
  }

  return ReactElement(type, key, ref, props);
};

6、jsx方法和createElement的区别

jsx函数和createElement函数都用于在React中创建虚拟DOM元素,但它们的语法和用法有所不同。jsx函数来自于React 17及更高版本中的新的JSX转换功能,称为"Runtime Automatic"。

1、语法和转换方式:jsx函数用于处理新的JSX转换方式,其语法更简洁。createElement函数用于处理传统的JSX转换方式

javascript 复制代码
const element = <div className="container">Hello, world!</div>;

// 使用createElement转换后的代码如下
const element = React.createElement(
  "div",
  { className: "container" },
  "Hello, world!"
);

// 使用jsx函数(自动运行时)转换后的代码如下:
import { jsx as _jsx } from "react/jsx-runtime";

const element = _jsx("div", { className: "container", children: "Hello, world!" });

2、子元素和key值处理:jsx函数将子元素作为属性(children)传递,而createElement函数将子元素作为额外的参数传递。同时子元素上的key值在jsx函数中也会以第三个参数的形式传递,而在createElement函数中,则是存在于config第二个参数中。

php 复制代码
// 在createElement函数中:
React.createElement("div", {className: "app", key: "appKey"}, "hello,app");


// 在jsx函数中:
import { jsx as _jsx } from "react/jsx-runtime";

_jsx("div", {className: "app", children: "hello,app"}, "appKey");

3、兼容性和版本:createElement函数在所有React版本中可用,而jsx函数仅在React 17及更高版本中提供。尽管React团队推荐使用新的JSX转换方式,但许多现有项目可能仍在使用createElement函数

相关推荐
cwj&xyp22 分钟前
Python(二)str、list、tuple、dict、set
前端·python·算法
dlnu201525062224 分钟前
ssr实现方案
前端·javascript·ssr
古木201928 分钟前
前端面试宝典
前端·面试·职场和发展
轻口味2 小时前
命名空间与模块化概述
开发语言·前端·javascript
前端小小王3 小时前
React Hooks
前端·javascript·react.js
迷途小码农零零发3 小时前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
娃哈哈哈哈呀3 小时前
vue中的css深度选择器v-deep 配合!important
前端·css·vue.js
旭东怪4 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
ekskef_sef5 小时前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端