实现一个jsxDEV方法

414 阅读3分钟

💡持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天

💡 代码在github: github.com/babachao/Re…

1.1 先写个简单的main.jsx

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>react18源码学习</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="./src/main.jsx"></script>
  </body>
</html>
// 入口文件
let element = (
  <h1 ref="h1">
    可爱的<span style={{ color: "#000" }}>羊巴鲁</span>
  </h1>
);
console.log(element);

💡 Tips: 1、实现一个jsxDEV方法,将main.jsx内容编译成图1。

2、在项目中新建src文件夹,再再src中新建main.jsx文件和react文件夹。

例:src/main.jsx image.png

1.2 jsx-dev-runtime.js

💡 Tip: 按照源码目录在src/react/jsx-dev-runtime.js

// babel会自动递归,生成ReactDOM
export { jsxDEV } from './src/jsx/ReactJSXElement';

1.3 ❗实现jsxDEV方法❗❗

1.3.1 公用方法:hasOwnProperty.js

💡 Tip: 在src新建---> src/shared/hasOwnProperty.js

// 将原型上的hasOwnProperty方法,结构出来使用
const { hasOwnProperty } = Object.prototype;
export default hasOwnProperty;

1.3.2 公用方法:ReactSymbols.js

💡 Tip: 在src新建---> src/shared/ReactSymbols.js

// 表示此虚拟DOM的类型是一个React元素
export const REACT_ELEMENT_TYPE = Symbol.for('react.element');

1.3.3 ReactJSXElement.js -核心内容

💡 Tip: 在src/react中新建---> src/react/src/jsx/ReactJSXElement.js

import hasOwnProperty from 'shared/hasOwnProperty';
import { REACT_ELEMENT_TYPE } from 'shared/ReactSymbols'

// 保留属性,为true的不会放到prop对象中
const RESERVED_PROPS = {
  key: true,
  ref: true,
  __self: true,
  __source: true,
};

/**
 * @description: 判断config中的key是否合法
 * @param {*} config jsxDev方法中的config参数
 */
function hasValidKey(config) {
  return config.key !== undefined;
}

/**
 * @description: 判断config中的ref是否合法
 * @param {*} config jsxDev方法中的config参数
 */
function hasValidRef(config) {
  return config.ref !== undefined;
}

/**
 * @description: 生成reactDOM,也是虚拟DOM
 * */
function ReactElement(type, key, ref, props) {
  return {
    $$typeof: REACT_ELEMENT_TYPE, // 代表这个虚拟DOM的类型是reactDOM
    type, // dom-> h1, div, span ~~
    key, // 唯一标识
    ref, // 作用是获取真实dom元素
    props, // children, id, style
  };
}

/**
 * @description: 把jsx转换为虚拟DOM的方法, jsxDev() = createElement()
 * @param {*} type tab标签名
 * @param {*} config children, id, style 等属性
 */
export function jsxDEV(type, config) {
  let propName; // 属性名
  const props = {}; // 属性对象
  let key = null; // 每个虚拟DOM可以有一个可选的key属性,用来区分一个父节点下的不同子节点
  let ref = null; // 引入,后面可以通过这个获取真实DOM
  // 判断config.key是否合法
  if (hasValidKey(config)) {
    key = config.key;
  }
  // 判断config.ref是否合法
  if (hasValidRef(config)) {
    ref = config.ref;
  }

  // 遍历config中的属性,将config上的属性拷贝到prop中
  for (propName in config) {
    // 1、在【RESERVED_PROPS】中将不需要的剔除
    if (
      hasOwnProperty.call(config, propName) &&
      !RESERVED_PROPS.hasOwnProperty(propName)
    ) {
      props[propName] = config[propName];
    }
  }
  return ReactElement(type, key, ref, props);
}

1.4 结构目录


react18源码学习
├─ index.html
├─ jsconfig.json
├─ package.json
├─ src
│  ├─ main.jsx
│  ├─ react
│  │  ├─ jsx-dev-runtime.js
│  │  └─ src
│  │     └─ jsx
│  │        └─ ReactJSXElement.js
│  └─ shared
│     ├─ ReactSymbols.js
│     └─ hasOwnProperty.js
└─ vite.config.js

1.5 总结

const babel = require('@babel/core');
const sourceCode = `
<h1 style="color:red;">
    <p>hello</p><span style={{ color: "red" }}>world</span>
</h1>`
const result = babel.transform(sourceCode, {
  // 老的为runtime: "classic" 
  // 新的为runtime: "automatic" 
  plugins: [["@babel/plugin-transform-react-jsx", { runtime: "classic" }]],
});
console.log(result.code);

1.5.1 React 17以前,babel转换是老的写法,转译结果:

1、可以发现React.createElement的参数是(标签,属性对象,子内容/子节点), 2、runtime参数的转换参数也是【classic】经典模式

/*#__PURE__*/
React.createElement("h1", {
  style: "color:red;"
}, /*#__PURE__*/React.createElement("p", null, "hello"), /*#__PURE__*/React.createElement("span", {
  style: {
    color: "red"
  }
}, "world"));

1.5.2 React17以后使用了新的转换写法

1、会自动导出jsx使用,runtime参数的转换参数也会改变为【automatic】自动模式 2、结构也变化为jsx(标签,属性对象+子节点),新转换的子节点都存放在children数组中,依此类推下去

import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";

/*#__PURE__*/
_jsxs("h1", {
  style: "color:red;",
  children: [
    /*#__PURE__*/_jsx("p", {
    children: "hello"
  }),
  /*#__PURE__*/_jsx("span", {
    style: {
      color: "red"
    },
    children: "world"
  })]
});

1.5.3 过程

  1. React.createElement 函数所返回的就是一个虚拟 DOM
  2. 虚拟 DOM 就是一个描述真实 DOM 的纯 JS 对象

虚拟DOM.jpeg