React源码解析————ReactElement.js
2021SC@SDUSC
2021SC@SDUSC
ReactElement.js
总览
ReactElement.js内部函数有:
│ ├── hasValidRef
│ ├── hasValidKey
│ ├── defineKeyPropWarningGetter
│ ├── defineRefPropWarningGetter
│ ├── warnIfStringRefCannotBeAutoConverted
│ ├── ReactElement
export的函数有:
│ ├── jsx
│ ├── jsxDEV
│ ├── createElement
│ ├── createFactory
│ ├── cloneAndReplaceKey
│ ├── cloneElement
│ ├── isValidElement
jsx和jsxDEV这两个函数的代码都可以基本归入createElement中,所以我会在createElement中一并讲解而不会单独分出一部分。
相关js函数
Object.getOwnPropertyDescriptor():
返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)ex:
const object1 = {
property1: 42
};
const descriptor1 = Object.getOwnPropertyDescriptor(object1, 'property1');
console.log(descriptor1.configurable);
// expected output: true
console.log(descriptor1.value);
// expected output: 42
hasOwnProperty():
返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。ex:
const object1 = {};
object1.property1 = 42;
console.log(object1.hasOwnProperty('property1'));
// expected output: true
console.log(object1.hasOwnProperty('toString'));
// expected output: false
console.log(object1.hasOwnProperty('hasOwnProperty'));
// expected output: false
Object.defineProperty():
直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。ex:
const object1 = {};
Object.defineProperty(object1, 'property1', {
value: 42,
writable: false
});
object1.property1 = 77;
// throws an error in strict mode
console.log(object1.property1);
// expected output: 42
hasValidRef
unction hasValidRef(config) {
if (__DEV__) {
if (hasOwnProperty.call(config, 'ref')) {
const getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.ref !== undefined;
}
通过Ref属性的取值器对象的isReactWarning属性检测是否含有合法的Ref,在开发环境下,如果这个props是react元素的props那么获取上面的ref就是不合法的,因为在creatElement的时候已经调用了defineRefPropWarningGetter。生产环境下如果config.ref !== undefined,说明合法。
hasValidKey
function hasValidKey(config) {
if (__DEV__) {
if (hasOwnProperty.call(config, 'key')) {
const getter = Object.getOwnPropertyDescriptor(config, 'key').get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.key !== undefined;
}
key 的验证方式和上述是一样的
defineKeyPropWarningGetter
function defineKeyPropWarningGetter(props, displayName) {
const warnAboutAccessingKey = function() {
if (__DEV__) {
if (!specialPropKeyWarningShown) {
specialPropKeyWarningShown = true;
console.error(
'%s: `key` is not a prop. Trying to access it will result ' +
'in `undefined` being returned. If you need to access the same ' +
'value within the child component, you should pass it as a different ' +
'prop. (https://reactjs.org/link/special-props)',
displayName,
);
}
}
};
warnAboutAccessingKey.isReactWarning = true;
Object.defineProperty(props, 'key', {
get: warnAboutAccessingKey,
configurable: true,
});
}
开发模式下,该函数在creatElement函数中可能被调用。锁定props.key的值使得无法获取props.key,标记获取props中的key值是不合法的,当使用props.key的时候,会执行warnAboutAccessingKey函数,进行报错,从而获取不到key属性的值。
即如下调用始终返回undefined
ex:props.key
给props对象定义key属性,以及key属性的取值器为warnAboutAccessingKey对象
该对象上存在一个isReactWarning为true的标志,在hasValidKey上就是通过isReactWarning来判断获取key是否合法
specialPropKeyWarningShown用于标记key不合法的错误信息是否已经显示,初始值为undefined。
defineRefPropWarningGetter
function defineRefPropWarningGetter(props, displayName) {
const warnAboutAccessingRef = function() {
if (__DEV__) {
if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true;
console.error(
'%s: `ref` is not a prop. Trying to access it will result ' +
'in `undefined` being returned. If you need to access the same ' +
'value within the child component, you should pass it as a different ' +
'prop. (https://reactjs.org/link/special-props)',
displayName,
);
}
}
};
warnAboutAccessingRef.isReactWarning = true;
Object.defineProperty(props, 'ref', {
get: warnAboutAccessingRef,
configurable: true,
});
}
逻辑与defineKeyPropWarningGetter一致,锁定props.ref的值使得无法获取props.ref,标记获取props中的ref值是不合法的,当使用props.ref的时候,会执行warnAboutAccessingKey函数,进行报错,从而获取不到ref属性的值。
warnIfStringRefCannotBeAutoConverted
didWarnAboutStringRefs = {};
function warnIfStringRefCannotBeAutoConverted(config) {
if (__DEV__) {
if (
typeof config.ref === 'string' &&
ReactCurrentOwner.current &&
config.__self &&
ReactCurrentOwner.current.stateNode !== config.__self
) {
const componentName = getComponentNameFromType(
ReactCurrentOwner.current.type,
);
if (!didWarnAboutStringRefs[componentName]) {
console.error(
'Component "%s" contains the string ref "%s". ' +
'Support for string refs will be removed in a future major release. ' +
'This case cannot be automatically converted to an arrow function. ' +
'We ask you to manually fix this case by using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: ' +
'https://reactjs.org/link/strict-mode-string-ref',
componentName,
config.ref,
);
didWarnAboutStringRefs[componentName] = true;
}
}
}
}
在开发环境下,如果配置中的 ref 属性是string,且ReactCurrentOwner的current和config的_self不为空且ReactCurrentOwner的stateNode和config的_self不相等,下面是ReactCurrentOwner以及其使用的Fiber的相关属性信息:
const ReactCurrentOwner = {
/**
* @internal
* @type {ReactComponent}
*/
current: (null: null | Fiber),
};
export type Fiber = {|
tag = WorkTag,
type: any,
stateNode: any,
|};
export type WorkTag =
| 0
| 1
| 2
| 3
| 4
| 5
| 6
| 7
| 8
| 9
| 10
| 11
| 12
| 13
| 14
| 15
| 16
| 17
| 18
| 19
| 20
| 21
| 22
| 23
| 24;
则调用getComponentNameFromType,该函数在开发环境下type:any且tag是number时报一个警告,声明这是一个unexpected的调用,:
if (__DEV__) {
if (typeof (type: any).tag === 'number') {
console.error(
'Received an unexpected object in getComponentNameFromType(). ' +
'This is likely a bug in React. Please file an issue.',
);
}
}
同时继续向下,接着报一个警告(因为未来不支持字符串传入 ref )
ReactElement
const ReactElement = function(type, key, ref, self, source, owner, props) {
const 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,
};
if (__DEV__) {
// 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;
};
被createElement函数调用,根据环境设置对应的属性。
代码性能优化:为提高测试环境下,element比较速度,将element的一些属性配置为不可数(enumerable: false),for…in还是Object.keys都无法获取这些属性,提高了速度。开发环境比生产环境多了_store,_self,_source属性,并且props以及element被冻结,无法修改配置。
$$typeof: REACT_ELEMENT_TYPE,能让我们判断这个元素是一个react元素
createElement
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;
if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
if (__DEV__) {
warnIfStringRefCannotBeAutoConverted(config);
}
}
if (hasValidKey(config)) {
key = '' + config.key;
}
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// 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];
}
}
}
// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
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__) {
if (Object.freeze) {
Object.freeze(childArray);
}
}
props.children = childArray;
}
// Resolve default props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
if (__DEV__) {
if (key || ref) {
const 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,
);
}
config 是参数传进来的配置信息,如果配置不为空:
1.如果配置中的 ref 有效,则将前面定义的 ref 赋值为配置中的 ref 属性
2.如果配置中的 key 有效,则将前面定义的 key 赋值为配置中的 key属性
3.如果配置中的私有变量 __self, __source不为空,则赋值。
除了上述提到的属性之外,遍历其余属性,赋值到之前定义的 prop 属性中
然后确定子元素的数量,因为 children 不一定只有一个,分情况讨论:
1.如果只有一个,那么就把这个 children 赋值给我们定义的 props 变量的 children 属性即可
2.如果大于一个,定义一个children的数组,将所有children都放进去,再将子元素数组赋值到 props 变量的 children 属性(开发环境,则冻结这个childArray数组(提升性能))
然后确定 type 参数中的 defaultProps属性是否为空,若不为空,如果之前我们定义的 props 中有属性为 undefined,则我们从defaultProps中提取默认值
开发环境下,若key或ref不为空,调用defineKeyPropWarningGetter或defineRefPropWarningGetter
createFactory
export function createFactory(type) {
const factory = createElement.bind(null, type);
// Expose the type on the factory and the prototype so that it can be
// easily accessed on elements. E.g. `<Foo />.type === Foo`.
// This should not be named `constructor` since this may not be the function
// that created the element, and it may not even be a constructor.
// Legacy hook: remove it
factory.type = type;
return factory;
}
返回一个函数,该函数生成给定类型的 React 元素。
用于将在字符串或者函数或者类转换成一个react元素,该元素的type为字符串或者函数或者类的构造函数
cloneAndReplaceKey
export function cloneAndReplaceKey(oldElement, newKey) {
const newElement = ReactElement(
oldElement.type,
newKey,
oldElement.ref,
oldElement._self,
oldElement._source,
oldElement._owner,
oldElement.props,
);
return newElement;
}
从一个旧的react元素身上clone它的type , ref, _self , _scource , _owner , props,得到的新的react元素并且设置新的key
cloneElement
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;
// Original props are copied
const props = Object.assign({}, element.props);
// Reserved names are extracted
let key = element.key;
let ref = element.ref;
// Self is preserved since the owner is preserved.
const self = element._self;
// Source is preserved since cloneElement is unlikely to be targeted by a
// transpiler, and the original source is probably a better indicator of the
// true owner.
const source = element._source;
// Owner will be preserved, unless ref is overridden
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;
}
// Remaining properties override existing 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[propName] = defaultProps[propName];
} else {
props[propName] = config[propName];
}
}
}
}
// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
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);
}
使用 element 作为起点,克隆并返回一个新的 React 元素。 所产生的元素将具有原始元素的 props ,新的 props 为浅层合并。 新的子元素将取代现有的子元素, key 和 ref 将被保留。
React.cloneElement() 几乎等效于:
<element.type {...element.props} {...props}>{children}</element.type>
然而,它也会保留 ref 。这意味着,如果你通过它上面的 ref 获取自己的子节点,你将不会有机会从你的祖先获取它。你只会获得绑定在你的新元素上的相同ref 。
isValidElement
export function isValidElement(object) {
return (
typeof object === 'object' &&
object !== null &&
object.$$typeof === REACT_ELEMENT_TYPE
);
}
判断一个对象是否是合法的react元素,即判断其$$typeof属性是否为REACT_ELEMENT_TYPE