blog
blog copied to clipboard
React的jsx是如何渲染成dom节点?
前言
本次阅读的React源码版本是17.0.1,了解一下JSX的相关知识
比较好奇的地方
- JSX被编译后生成了什么?
- JSX生成的对象有哪些属性?
- JSX编译生成的ReactElement对象的作用是什么?
JSX被编译后生成了什么?
使用plugin-transform-react-JSX
的babel插件,可以将JSX语法转换成js语法,转换成React.createElement
函数去执行。
例如,假设JSX代码如下
<div>hello world</div>
经过babel编译后,会转换成js代码。可以去babel官网直接进行转换。
React.createElement("div", null, "hello world");
JSX生成的对象有哪些属性?
React17对JSX进行优化,不再需要React环境,经过babel编译后会自动引入JSX运行时,即React.createElement
方法。
React17将React.createElement
单独抽离成一个文件,放在React目录下的JSX-runtime.js
文件。
假如JSX代码如下
function App() {
return <h1>Hello World</h1>;
}
下面是最新JSX被转换编译的结果
// 由编译器引入(禁止自己引入!)
import {JSX as _JSX} from 'react/jsx-runtime';
function App() {
return _JSX('h1', { children: 'Hello world' });
}
下面我们来看一下React.createElement
方法的源码,即jsx-runtime.js
中的暴露的JSX方法。
// 即 React.createElement方法
function JSXDEV(type, config, maybeKey, source, self) {
{
var propName; // Reserved names are extracted
var props = {};
var key = null;
var ref = 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;
}
if (hasValidRef(config)) {
ref = config.ref;
warnIfStringRefCannotBeAutoConverted(config, self);
} // Remaining properties are added to a new props object
for (propName in config) {
if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
} // Resolve default props
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
if (key || ref) {
var displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type;
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}
}
可以看到,经过对传入参数的简单修饰,最后会调用ReactElement方法,而ReactElement方法会返回一个对象,即JSX对象。
var ReactElement = function (type, key, ref, self, source, owner, props) {
var 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: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner
};
{
// The validation flag is currently mutative. We put it on
// an external backing store so that we can freeze the whole object.
// This can be replaced with a WeakMap once they are implemented in
// commonly used development environments.
element._store = {}; // To make comparing ReactElements easier for testing purposes, we make
// the validation flag non-enumerable (where possible, which should
// include every environment we run tests in), so the test framework
// ignores it.
Object.defineProperty(element._store, 'validated', {
configurable: false,
enumerable: false,
writable: true,
value: false
}); // self and source are DEV only properties.
Object.defineProperty(element, '_self', {
configurable: false,
enumerable: false,
writable: false,
value: self
}); // Two elements created in two different places should be considered
// equal for testing purposes and therefore we hide it from enumeration.
Object.defineProperty(element, '_source', {
configurable: false,
enumerable: false,
writable: false,
value: source
});
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}
return element;
};
JSX对象有以下几个属性:
-
$$typeof
。使用Symbol来标记每个React元素,防止XSS攻击。 -
type
。组件标签名称 -
key
。组件的key属性 -
ref
。组件的ref属性 -
props
。组件的属性 -
_owner
。拥有这个组件的父级fiber节点。
比如
<marquee bgcolor="#ffa7c4">hi</marquee>
转换成如下的JSX对象
{
type: 'marquee',
props: {
bgcolor: '#ffa7c4',
children: 'hi',
},
key: null,
ref: null,
$$typeof: Symbol.for('react.element')
}
编译生成的ReactElement对象的作用是什么?
JSX语法会被编译成js语法。而JSX语法主要在以下两个地方使用,React.createElement
也就是在这时候执行。
-
ReactDom.render
里面传入的参数<APP/>
- 组件的render方法返回的JSX
下面我们来看一下第二种情况,即组件render方法执行之后,生成的ReactElement对象用来做什么?
首次渲染会生成workInProgress fiber
树,render方法在生成组件fiber节点的时候调用的,具体是在finishClassComponent
方法中执行。
function finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderLanes) {
//...
var nextChildren;
if (didCaptureError && typeof Component.getDerivedStateFromError !== 'function') {
nextChildren = null;
{
stopProfilerTimerIfRunning();
}
} else {
{
setIsRendering(true);
// 执行组件的render方法
nextChildren = instance.render();
if ( workInProgress.mode & StrictMode) {
disableLogs();
try {
instance.render();
} finally {
reenableLogs();
}
}
setIsRendering(false);
}
}
workInProgress.flags |= PerformedWork;
if (current !== null && didCaptureError) {
// If we're recovering from an error, reconcile without reusing any of
// the existing children. Conceptually, the normal children and the children
// that are shown on error are two different sets, so we shouldn't reuse
// normal children even if their identities match.
forceUnmountCurrentAndReconcile(current, workInProgress, nextChildren, renderLanes);
} else {
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
workInProgress.memoizedState = instance.state; // The context might have changed so we need to recalculate it.
if (hasContext) {
invalidateContextProvider(workInProgress, Component, true);
}
return workInProgress.child;
}
然后执行reconcileChildren
方法,最后经过一长串的调用栈,通过调用createFiberFromElement
方法,将ReactElement对象转换成组件的fiber节点。
function createFiberFromElement(element, mode, lanes) {
var owner = null;
{
owner = element._owner;
}
var type = element.type;
var key = element.key;
var pendingProps = element.props;
var fiber = createFiberFromTypeAndProps(type, key, pendingProps, owner, mode, lanes);
{
fiber._debugSource = element._source;
fiber._debugOwner = element._owner;
}
return fiber;
}
最后,转换生成的fiber节点会在commit阶段渲染成相应的dom节点。