path:packages/react/src/ReactElement.js
ReactElement 下面导出了多个方法:
createElement用于创建并返回一个新的ReactElement 元素createFactory用于返回固定类的 createElement 方法 【已废弃】cloneElement克隆一个元素isValidElement验证一个对象是否为 ReactElement 。cloneAndReplaceKey使用给定 key 返回一个新的 ReactElement 类型
ReactElement
ReactElement 是用来创建 React 元素的工厂方法。
ReactElemen 不再遵循类的模式,并未从 ReactElement 模块导出,因此不能使用 new 来调用它,并且无法使用 instanceOf 检查。
取而代之,可以检测 ?typeof 字段与 Symbol.for('react.element') 是否匹配来判断是否是一个 React 元素
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
?typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
props: props,
_owner: owner,
};
if (__DEV__) {
// do something
}
return element;
};
从上面的源码中可以看到 React 元素是一个拥有以下属性的普通的对象:
-
?typeof这个标记允许我们唯一地将其标识为 React 元素,其值为
REACT_ELEMENT_TYPE。我们可以通过判断对象的
?typeof是否等于REACT_ELEMENT_TYPE来判断其是否为一个 React 元素。 -
type: -
key: -
ref: -
props: -
_owner:
createElement
React.createElement 函数在 React 中具有举足轻重的地位,我们的组件最后都会编译成它。
React.createElement 使用给定的 type 创建并返回的新的 React 元素,参见
React 官方文档。
export function createElement(type, config, children) {
let propName;
// Reserved names are extracted
const props = {};
let key = null;
let ref = null;
let self = null;
let source = null;
// 不为 undefined 和 null
if (config != null) {
// config 是否含有有效的 ref 属性
if (hasValidRef(config)) {
ref = config.ref;
}
// config 是否含有有效的 key 属性
if (hasValidKey(config)) {
key = '' + config.key;
}
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// 剩余的属性被添加到一个新的 props 对象中
// RESERVED_PROPS 包含四个属性 ref、key、__self、__source
// 这里就是拷贝除这四个属性之外的其他属性
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}
// children 可以是多个参数,这些参数被转移到新分配的 props 对象上。
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
if (__DEV__) {
// do somthing
}
props.children = childArray;
}
// 解析默认 props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
if (__DEV__) {
// do something
}
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
示例
来看看一个示例:
React.createElement('div');
返回一个对象,对象如下:

可以看到句代码创建了一个普通的 javascript 对象。这时候,产生了疑问:
- DOM 层级如此之多,信息之复杂,React 又是如何实现的?
React 如何实现 DOM 层级
来看下面的 Hello 组件:
function Hello(props) {
function sayHi() { return (<div>Hi</div>); }
function handleClick(e) {
console.log('click');
}
return (
<div>
{ sayHi() }
Hello { props.name }
<p className="title" onClick={ handleClick }>React</p>
<p className="content">React is cool!</p>
<MyFooter className="footer">
<div className="concat">concat</div>
<div className="info">company info</div>
</MyFooter>
</div>
);
}
MyFooter 组件:
function MyFooter(props) {
return (
<div className="footer">
{ props.children }
</div>
);
}
我们采用了函数式组件的方式,他们将分别被编译为:
// Hello 组件
function Hello(props) {
function sayHi() {
return React.createElement("div", null, "Hi");
}
function handleClick(e) {
console.log('click');
}
return React.createElement("div", null, sayHi(), "Hello ", props.name, React.createElement("p", {
className: "title",
onClick: handleClick
}, "React"), React.createElement("p", {
className: "content"
}, "React is cool!"), React.createElement(MyFooter, {
className: "footer"
}, React.createElement("div", {
className: "concat"
}, "concat"), React.createElement("div", {
className: "info"
}, "company info")));
}
// MyFooter 组件
function MyFooter(props) {
return React.createElement("div", {
className: "footer"
}, props.children);
}
首先,从上面的代码我们可以看出下面几点:
-
组件都会被编译成
React.createElement不论是自定义组件还是原始的 html 标签,都会被编译器编译。 -
React.createElement方法的参数个数是可变的,在上面源码分析中,我们已经看到从第三个参数开始的所有参数会打包为一个数组,存入 React 元素的props属性的children中。 -
不论从组件的哪一级部分开始划分,其子元素都是通过函数参数传递进父级的,并最后都会存放于
props属性的children中。 -
在处理
children时,编译器会很智能地区分字符串、{} 表达式和 JSX,不同的部分都会被转换成一个React.createElement的参数,因此在上面的代码中,会产生 6 个参数:sayHi()"Hello"props.nameReact.createElement(...)React.createElement(...)React.createElement(...)
-
自定义组件的
type参数值是组件的直接引用,而不是组件名的字符串。MyFooter组件的type属性是一个函数,是它本身,而不是"MyFooter"字符串。 -
只要是写在 JSX 上的属性,都被当做
config的一部分传递给了React.createElement(),包括事件,例如这里的onClick
再看看生成的 JavaScript 对象:

所谓的层级结构就是通过 React 元素的 props 属性中的 children 属性层层嵌套得来的。
createFactory
此辅助函数已废弃,建议使用 JSX 或直接调用 React.createElement() 来替代它。
cloneAndReplaceKey
使用新的 key 克隆一个 React 元素。
export function cloneAndReplaceKey(oldElement, newKey) {
const newElement = ReactElement(
oldElement.type,
newKey,
oldElement.ref,
oldElement._self,
oldElement._source,
oldElement._owner,
oldElement.props,
);
return newElement;
}
cloneElement
克隆一个旧的 react 元素,并返回一新的个 React 元素,参见 React 官方文档。
export function cloneElement(element, config, children) {
invariant(
!(element === null || element === undefined),
'React.cloneElement(...): The argument must be a React element, but you passed %s.',
element,
);
let propName;
// 复制原始 props
const props = Object.assign({}, element.props);
// 提取保留名称
let key = element.key;
let ref = element.ref;
// Self 被保存,因为 owner 被保存。
const self = element._self;
// 保存 Source,因为 cloneElement 不太可能被 transpiler 定位,
// 而原始源 source 可能是真正 owner 的更好指示器。
const source = element._source;
// Owner 将被保留,除非 ref 被覆盖
let owner = element._owner;
if (config != null) {
if (hasValidRef(config)) {
// Silently steal the ref from the parent.
ref = config.ref;
owner = ReactCurrentOwner.current;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
// 其余属性覆盖现有的 props
let defaultProps;
if (element.type && element.type.defaultProps) {
defaultProps = element.type.defaultProps;
}
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
if (config[propName] === undefined && defaultProps !== undefined) {
// Resolve default props
// 解析默认 props
props[propName] = defaultProps[propName];
} else {
props[propName] = config[propName];
}
}
}
}
// Children 可以是多个参数,这些参数被转移到新分配的 props 对象上。
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}
return ReactElement(element.type, key, ref, self, source, owner, props);
}
cloneElement 方法会拷贝对旧 React 元素的属性进行拷贝,并使用新传入的 config 和 children 参数对这些属性进行更新。然后使用这些新的属性作为参数调用 ReactElement 构造方法,生成并返回新的 React 元素。
isValidElement
通过判断对象的 ?typeof 属性与 Symbol.for('react.element') 是否相同来,验证一个对象是否为 React 元素。
通过 React.createElement 方法创建的对象,都含有一个值完全相同的 ?typeof 属性,标识其为一个 React 元素。
export function isValidElement(object) {
return (
typeof object === 'object' &&
object !== null &&
object.?typeof === REACT_ELEMENT_TYPE
);
}
其他
RESERVED_PROPS
保留属性
const RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true,
};
hasValidRef
hasValidRef 判断一个对象中是否含有 ref 属性
function hasValidRef(config) {
if (__DEV__) {
// do something
}
return config.ref !== undefined;
}
hasValidKey
hasValidKey 判断一个对象中是否含有 key 属性
function hasValidKey(config) {
if (__DEV__) {
// do something
}
return config.key !== undefined;
}
遗留问题
- 普通的 javascript 对象(React 元素)是如何变成了我们页面的 DOM 结构的
写文章的时候再阅读 React 的官方文档,发现这份文档制作很用心,循序渐进,由浅入深。
借用文档的一句话:
我们将在下一章节中探讨如何将 React 元素渲染为 DOM。