React源码解析(一):JSX语法转换

254 阅读2分钟

前言

最近抽出一个月的时间,在各位大佬的文章的帮助下磕磕绊绊过了一遍react源码,当然所见并非所得,因此希望能总结出几篇文章用以梳理其中的一些知识点。
本篇是React源码解析系列第一篇。主要学习JSX语法转换过程和实现JSX构建虚拟DOM树。源码版本为v18.2.0。

JSX语法

JSX语法的确是一个非常伟大的发明,它使得DOM和JS不再割裂开来,使用非常灵活、简单,代码十分易读,任何人使用过后免不了要赞叹一句”真香!“。
当然JSX语法本质也只是一个语法糖,并不能直接被浏览器解析,需要借助babel工具将其转换为浏览器能读懂的js代码。
JSX语法转换有ClassicAutomatic两种类型。

React 17之前

在React17之前我们如果将代码中React的引用去掉的话,控制台会报出React must be in scope when using JSX的语法错误。这是因为babel会将JSX语法转换为React.createElement的js代码,因此我们必须要手动引入React来保证其引用。这种转换又被称为Classic类型,即传统类型。我们可以通过babel官网的Try it out来查看这种转换。

image.png

React 17之后

在React17之后我们不再需要手动引用React,因为 @babel/preset-react 预设中的 @babel/plugin-transform-react-jsx插件采用了 Automatic 的runtime转换形式。 我们可以在babel官网试用页面的左栏设置中采用 Automatic

image.png

image.png

转换的结果

我们可以直接通过console.log打印出JSX的转换结果。

const Element = (
    <h1 style={{color:"red"}}>Hello World</h1>
)
console.log(Element);

image.png

可以看到转换后的对象实际是一棵树,这棵树描述了这个组件的dom结构,我们称其为虚拟DOM。 它包含了以下属性:

  • $$typeof: react元素类型
  • key: 元素标识,用来dom-diff使用
  • props: 元素属性,包含了children、style等定义在节点上的属性,其中children包含了子节点
  • ref: 元素的引用
  • type: 元素类型,普通DOM节点是DOM类型字符串,函数组件和类组件则是自身函数或类的定义。

实现JSX构建虚拟DOM树

总体实现也非常简单,因为babel解析后每个组件/DOM标签处都会调用jsx方法去构建虚拟DOM,因此我们完全不需要关心递归的问题,只需要将jsx方法中拿到的此虚拟DOM的内容组合为一个对象即可。


const hasOwnProperty = Object.prototype.hasOwnProperty;
// React元素类型标识
const REACT_ELEMENT_TYPE = Symbol.for("react.element");
// 保留在最外层的属性 不放到props中
const RESERVED_PROPS = {
  key: true,
  ref: true,
  __self: true,
  __source: true,
};
/** 
    新版JSX编译后第三个参数为key,但旧版的key可能放在第二个参数config中
*/
export function jsx(type, config, maybeKey) {
  let propName;
  const props = {};
  let key = null;
  let ref = null;
  // key赋值
  if (maybeKey !== undefined) {
    key = '' + maybeKey;
  }
  if (hasValidKey(config)) {
    key = '' + config.key;
  }
  // ref赋值
  if (hasValidRef(config)) {
    ref = config.ref;
  }
  // 将其他属性放到props中
  for (propName in config) {
  // 剔除掉保留的key
    if (
      hasOwnProperty.call(config, propName) &&
      !RESERVED_PROPS.hasOwnProperty(propName)
    ) {
      props[propName] = config[propName];
    }
  }

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

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

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


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