前言
最近抽出一个月的时间,在各位大佬的文章的帮助下磕磕绊绊过了一遍react源码,当然所见并非所得,因此希望能总结出几篇文章用以梳理其中的一些知识点。
本篇是React源码解析系列第一篇。主要学习JSX语法转换过程和实现JSX构建虚拟DOM树。源码版本为v18.2.0。
JSX语法
JSX语法的确是一个非常伟大的发明,它使得DOM和JS不再割裂开来,使用非常灵活、简单,代码十分易读,任何人使用过后免不了要赞叹一句”真香!“。
当然JSX语法本质也只是一个语法糖,并不能直接被浏览器解析,需要借助babel工具将其转换为浏览器能读懂的js代码。
JSX语法转换有Classic和Automatic两种类型。
React 17之前
在React17之前我们如果将代码中React的引用去掉的话,控制台会报出React must be in scope when using JSX的语法错误。这是因为babel会将JSX语法转换为React.createElement的js代码,因此我们必须要手动引入React来保证其引用。这种转换又被称为Classic类型,即传统类型。我们可以通过babel官网的Try it out来查看这种转换。
React 17之后
在React17之后我们不再需要手动引用React,因为 @babel/preset-react 预设中的 @babel/plugin-transform-react-jsx插件采用了 Automatic 的runtime转换形式。 我们可以在babel官网试用页面的左栏设置中采用 Automatic 。
转换的结果
我们可以直接通过console.log打印出JSX的转换结果。
const Element = (
<h1 style={{color:"red"}}>Hello World</h1>
)
console.log(Element);
可以看到转换后的对象实际是一棵树,这棵树描述了这个组件的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,
};
}