React实现系列一 - jsx

826 阅读2分钟

从0实现React18系列一

本系列是讲述从0开始实现一个react18的基本版本。由于React源码通过Mono-repo 管理仓库,我们也是用pnpm提供的workspaces来管理我们的代码仓库,打包我们使用rollup进行打包。

仓库地址

系列文章:

  1. React实现系列一 - jsx
  2. 从0实现React18系列二-reconciler
  3. 从0实现React18系列三-打标记
  4. 从0实现React18系列四-commit
  5. 从0实现React18系列五-update流程
  6. 从0实现React18系列六-dispatch update流程
  7. 从0实现React18系列七-事件系统
  8. 从0实现React18系列八-同级节点diff

基本概念

我们知道React是一个应用级框架,每一次更新都是从应用根节点开始向下调和,相比于元素级Svelte和组件级的Vue, React甚至不需要确定哪一个自变量发生了变化。基于React的内部优化以及提供开发者一些减少无意义的遍历过程,性能也十分优秀。

React主要是将页面的结构通过jsx进行描述,在调和后,每一个 React element对象的子节点都会形成一个对应的fiberNode

本节内容主要是实现jsx的生成。在React的源码中,jsx的代码逻辑存在packages下面的react包中。为了兼容React的旧版本,我们主要是实现最后导出三个文件。

  1. index.js: import React from 'react' 这样使用
  2. jsx-runtime.js: 新版通过babel导入
  3. jsx-dev-runtime.js: 开发环境的包

jsx

为了开发者方便,React提供一种类似于html的方式去书写代码,然后React通过babel去进行转义。在React的新版本中,我们不再需要手动去引入React, plugin-syntax-jsx 已经向文件中提前注入了 _jsxRuntime api

例如我们这一段代码,经过babel转移后调用React的内部方法,将其转换成ReactElement对象。

<div className="hcc">
  123
  <span>yx</span>
</div>
// 新版Automatic
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
/*#__PURE__*/_jsxs("div", {
  className: "hcc",
  children: ["123", /*#__PURE__*/_jsx("span", {
    children: "yx"
  })]
});
// 老版Classic
/*#__PURE__*/React.createElement("div", {
  className: "hcc"
}, "123", /*#__PURE__*/React.createElement("span", null, "yx"));

主要是分为三部分:1. 对应的tag字段, 2. 属性和children, 3. key等一些特殊字段。

实现

在开发环境,babel会引入React中jsx-dev-rumtime.js文件,通过调用里面的jsxDEV方法,生成对应的ReactElement。所以我们需要实现一个对应的方法。

const ReactElement = function (
  type: Type,
  key: Key,
  ref: Ref,
  props: Props
): ReactElementType {
  const element = {
    $$typeof: REACT_ELEMENT_TYPE,
    type,
    key,
    ref,
    props,
    __mark: "hcc",
  };
  return element;
};

export const jsxDEV = (type: ElementType, config: any) => {
  let key: Key = null;
  const props: Props = {};
  let ref: Ref = null;
  for (const prop in config) {
    const val = config[prop];
    if (prop === "key") {
      if (val !== undefined) {
        key = "" + val;
      }
      continue;
    }
    if (prop === "ref") {
      if (val !== undefined) {
        ref = val;
      }
      continue;
    }
    if ({}.hasOwnProperty.call(config, prop)) {
      props[prop] = val;
    }
  }
  return ReactElement(type, key, ref, props);
};

主要是找到一些特殊的字段(keyref等),然后传入ReactElement中, 生成ReactElement。具体的打包流程可以查看代码仓库。