Skip to main content

实现 JSX

info
  • 源码目录结构分析
  • 实现 JSX 方法`
  • 相关代码可在 git tag v1.1 查看

源码目录结构

React使用的是 Mono-repo 的结构管理各个包。

源码主要包括如下部分:

  • fixtures: 测试用例
  • packages: 主要部分,包含 scheduler,reconciler 等。
  • scripts: react 构建相关。

其中,主要的包在 packages 目录下,主要包含以下模块:

  • react: 核心 Api 所在,如 React.createElement、React.Component
  • react-reconclier: 协调器, 在 render 阶段用来构建 fiber 节点,和宿主环境无关
  • scheduler: 调度器相关
  • react-server: ssr 相关
  • react-interactions: 和事件如点击事件相关
  • 各种宿主环境的包:
    • react-dom: 浏览器环境
    • react-native-renderer:原生环境
    • react-art: canvas & svg 渲染
    • react-noop-renderer: 调试或 fiber 用
  • 辅助包:
    • shared: 公用辅助方法,宿主环境无关
    • react-is : 判断类型
    • react-client: 流相关
    • react-fetch: 数据请求相关
    • react-refresh: 热加载相关

JSX 介绍

React中使用JSX语法描述用户界面。

JSX 语法就是一种语法糖,是 一种 JavaScript 语法扩展,它允许开发者在 JavaScript 代码中直接编写类似 HTML 的代码,并在运行时将其转换为 React 元素。

// JSX 源代码
import React from "react";

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

JSX 转换的过程大致分为两步:

  • 编译时:由 Babel 编译实现, Babel 会将 JSX 语法转换为标准的 JavaScript API;
  • 运行时:由 React 实现,jsx 方法 和 React.createElement 方法;

因此,我们只需要实现运行时的部分即可,即 jsx 方法和 React.createElement 方法。

初始化 react

先在packages目录, 新建 react 文件夹。执行 yarn init

cd packages/react

yarn init

package.json 如下所示:

package/react/package.json
{
"name": "react",
"version": "1.0.0",
"description": "React is a JavaScript library for building user interfaces.",
"main": "index.js",
"keywords": [],
"author": "",
"exports": {
".": {
"default": "./index.js"
},
"./package.json": "./package.json",
"./jsx-runtime": "./jsx-runtime.js",
"./jsx-dev-runtime": "./jsx-dev-runtime.js",
"./src/*": "./src/*"
},
"license": "ISC"
}

react 目录结构

这里参考 React 官方的目录结构。基本目录如下所示:

react
└── npm
└── index.js
└── jsx-dev-runtime.js
└── jsx-runtime.js
└── src
└── jsx
└── ReactJSXElement.js
└── React.ts
└── index.js
└── jsx-dev-runtime.js
└── jsx-runtime.js
└── package.json
└── README.md

实现 JSX 方法

  • $$typeof: 这个字段可以用来标记是否是 React 元素。
  • type: 用来表示 React 元素或者组件的名称。
packages/react/src/jsx/ReactJSXElement.js
import { REACT_ELEMENT_TYPE } from "shared/ReactSymbols";

function getOwner() {
return null;
}

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

/**
* 创建新的 React 元素的工厂方法.
* 这个函数不再遵守类模式, 所以不要使用new来调用它。
* 另外,instanceof 检查不会工作.
* 根据 Symbol.for('react.element') 测试 $$typeof 字段来检查是否是 React 元素
*
* @param {*} type
* @param {*} props
* @param {*} key
* @param {string|object} ref
* @param {*} owner
* @param {*} self A *temporary* helper to detect places where `this` is
* different from the `owner` when React.createElement is called, so that we
* can warn. We want to get rid of owner and replace string `ref`s with arrow
* functions, and as long as `this` and owner are the same, there will be no
* change in behavior.
* @param {*} source An annotation object (added by a transpiler or otherwise)
* indicating filename, line number, and/or other information.
* @internal
*/
function ReactElement(type, key, self, source, owner, props) {
// Ignore whatever was passed as the ref argument and treat `props.ref` as
// the source of truth. The only thing we use this for is `element.ref`,
// which will log a deprecation warning on access. In the next release, we
// can remove `element.ref` as well as the `ref` argument.
const refProp = props.ref;

// An undefined `element.ref` is coerced to `null` for
// backwards compatibility.
const ref = refProp !== undefined ? refProp : null;

// In prod, `ref` is a regular property and _owner doesn't exist.
let element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,

// Built-in properties that belong on the element
type,
key,
ref,

props,
};

return element;
}

/**
* https://github.com/reactjs/rfcs/pull/107
* @param {*} type
* @param {object} props
* @param {string} key
*/
export function jsx(type, config, maybeKey) {
let key = null;

// Currently, key can be spread in as a prop. This causes a potential
// issue if key is also explicitly declared (ie. <div {...props} key="Hi" />
// or <div key="Hi" {...props} /> ). We want to deprecate key spread,
// but as an intermediary step, we will use jsxDEV for everything except
// <div {...props} key="Hi" />, because we aren't currently able to tell if
// key is explicitly declared to be undefined or not.
if (maybeKey !== undefined) {
key = "" + maybeKey;
}

if (hasValidKey(config)) {
key = "" + config.key;
}

let props;
if (!("key" in config)) {
// If key was not spread in, we can reuse the original props object. This
// only works for `jsx`, not `createElement`, because `jsx` is a compiler
// target and the compiler always passes a new object. For `createElement`,
// we can't assume a new object is passed every time because it can be
// called manually.
//
// Spreading key is a warning in dev. In a future release, we will not
// remove a spread key from the props object. (But we'll still warn.) We'll
// always pass the object straight through.
props = config;
} else {
// We need to remove reserved props (key, prop, ref). Create a fresh props
// object and copy over all the non-reserved props. We don't use `delete`
// because in V8 it will deopt the object to dictionary mode.
props = {};
for (const propName in config) {
// Skip over reserved prop names
if (propName !== "key") {
props[propName] = config[propName];
}
}
}

return ReactElement(
type,
key,
undefined,
undefined,
getOwner(),
props,
undefined,
undefined
);
}

创建 share 目录

我们先在packages目录, 新建 shared 文件夹。执行 yarn init

cd packages/shared

yarn init

shared 包不需要入口文件,因为它里面的所有方法都会直接在其他包里面被引用,代码如下:

packages/shared/package.json
{
"private": true,
"name": "shared",
"version": "0.0.0"
}