ReactElement.js 파일을 분석해 보자.
hasValidRef
function hasValidRef(config) {
if (process.env.NODE_ENV !== 'production') {
if (hasOwnProperty.call(config, 'ref')) {
var getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.ref !== undefined;
}
React Element가 생성될 때, defineRefPropWarning가 호출되기 때문에, React Develop mode에서 props가 React Element의 props일 때, ref를 참조하는 것은 불법이다.
또한, 우리는 추가적으로 Production mode에서 리엑트 경고 메시지를 출력하지 않는 것을 알 수 있다. 리 엑트 공식문서에도 나와있듯이 경고 메시지가 없어지는 것만으로도 많은 성능 개선을 할 수 있다.
defineRefPropWarningGetter
function defineRefPropWarningGetter(props, displayName) {
var warnAboutAccessingRef = function () {
if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true;
process.env.NODE_ENV !== 'production' ? warning(false, '%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://fb.me/react-special-props)', displayName) : void 0;
}
};
warnAboutAccessingRef.isReactWarning = true;
Object.defineProperty(props, 'ref', {
get: warnAboutAccessingRef,
configurable: true
});
}
Develop 모드에서 creatElement 함수에 의해 호출된다. props로 ref를 얻지 못하게 잠근다. ref를 사용할때, 아래 코드로 오류를 보고한다.
warnAboutAccessingRef.isReactWarning = true;
다음 호출은 항상 undefined를 반환합니다.
props.ref
key도 ref와 동일하다.
function hasValidKey(config) {
if (process.env.NODE_ENV !== 'production') {
if (hasOwnProperty.call(config, 'key')) {
var getter = Object.getOwnPropertyDescriptor(config, 'key').get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.key !== undefined;
}
function defineKeyPropWarningGetter(props, displayName) {
var warnAboutAccessingKey = function () {
if (!specialPropKeyWarningShown) {
specialPropKeyWarningShown = true;
process.env.NODE_ENV !== 'production' ? warning(false, '%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://fb.me/react-special-props)', displayName) : void 0;
}
};
warnAboutAccessingKey.isReactWarning = true;
Object.defineProperty(props, 'key', {
get: warnAboutAccessingKey,
configurable: true
});
}
다음과 같이 key와 ref를 props에서 참조하지 않게한 이유는 Special Props Warning에서 그 이유를 찾아볼 수 있다.
While this may seem redundant, it’s important to separate app logic from reconciling hints.
React의 Reconciliation 최적화에 도움을 주기 때문이다.
ReactElement
/**
* Factory method to create a new React element. This no longer adheres to
* the class pattern, so do not use new to call it. Also, no instanceof check
* will work. Instead test $$typeof field against Symbol.for('react.element') to check
* if something is a React Element.
*
* @param {*} type
* @param {*} key
* @param {string|object} ref
* @param {*} self A *temporary* helper to detect places where `this` is
* different from the `owner` when React.createElement is called, so that we
* can warn. We want to get rid of owner and replace string `ref`s with arrow
* functions, and as long as `this` and owner are the same, there will be no
* change in behavior.
* @param {*} source An annotation object (added by a transpiler or otherwise)
* indicating filename, line number, and/or other information.
* @param {*} owner
* @param {*} props
* @internal
*/
var ReactElement = function (type, key, ref, self, source, owner, props) {
var element = {
// This tag allow 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 (process.env.NODE_ENV !== 'production') {
// 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.
if (canDefineProperty) {
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
});
} else {
element._store.validated = false;
element._self = self;
element._source = source;
}
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}
return element;
};
ReactElement는 createElement 함수에 의해 호출된다. ReactElement는 코드만 길지 간단하다. type, key, props, ref를 element로 갖고 이를 반환한다.
Develop mode는 Production mode보다 더 많은 _store, _self, _source 속성을 갖으며, props와 element가 freeze 된다. 즉, ReactElement는 immutable 하다.
createElement
/**
* Create and return a new ReactElement of the given type.
* See https://facebook.github.io/react/docs/top-level-api.html#react.createelement
*/
ReactElement.createElement = function (type, config, children) {
var propName;
// Reserved names are extracted
var props = {};
var key = null;
var ref = null;
var self = null;
var source = null;
if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
}
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.
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
if (process.env.NODE_ENV !== 'production') {
if (Object.freeze) {
Object.freeze(childArray);
}
}
props.children = childArray;
}
// Resolve default props
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
if (process.env.NODE_ENV !== 'production') {
if (key || ref) {
if (typeof props.$$typeof === 'undefined' || props.$$typeof !== REACT_ELEMENT_TYPE) {
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);
};
JSX로 적힌 코드는 대부분의 경우 React.createElement()로 변환된다.
Type Parameter는 'div', 'span' 같은 tag name 문자열이거나, React Component의 Function 혹은 Class, React Fragment 일 수 있다. 또한, 우리는 위에서 살펴본 함수를 토대로 아래 코드에서 개발 모드일 때, props가 react component의 props이면 key와 ref를 참조할 때 오류가 발생함을 알 수 있다.
if (hasValidRef(config)) {
ref = config.ref;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
Children Parameter가 존재할 때, props.children에 덮어써준다. 해당 관련 실험은 아래 Github 링크를 참고하자.
// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
if (process.env.NODE_ENV !== 'production') {
if (Object.freeze) {
Object.freeze(childArray);
}
}
props.children = childArray;
}
https://github.com/hochan222/Under-the-hood-ReactJS#createelement-children
defaultProps도 createElement에서 초기화가 이루어진다.
// Resolve default props
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
createFactory
/**
* Return a function that produces ReactElements of a given type.
* See https://facebook.github.io/react/docs/top-level-api.html#react.createfactory
*/
ReactElement.createFactory = function (type) {
var factory = ReactElement.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 TODO: Warn if this is accessed
factory.type = type;
return factory;
};
createFactory는 주어진 type의 React Element를 생성하는 함수를 반환한다.
https://github.com/hochan222/Under-the-hood-ReactJS#createfactory
React 공식문서에서는 createFactory()는 legacy라고 한다. createFactory() 대신에 React.createElement()를 사용하자.
This helper is considered legacy, and we encourage you to either use JSX or use React.createElement() directly instead. - React docs: createFactory -
cloneAndReplaceKey
인자로 주어진 새로운 Key로 ReactElement를 만들어서 반환해준다. (참고로 현재 최신 버전 공식문서에는 명시되어있지 않다.) 참고
ReactElement.cloneAndReplaceKey = function (oldElement, newKey) {
var newElement = ReactElement(oldElement.type, newKey, oldElement.ref, oldElement._self, oldElement._source, oldElement._owner, oldElement.props);
return newElement;
};
cloneElement
/**
* Clone and return a new ReactElement using element as the starting point.
* See https://facebook.github.io/react/docs/top-level-api.html#react.cloneelement
*/
ReactElement.cloneElement = function (element, config, children) {
var propName;
// Original props are copied
var props = _assign({}, element.props);
// Reserved names are extracted
var key = element.key;
var ref = element.ref;
// Self is preserved since the owner is preserved.
var 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.
var source = element._source;
// Owner will be preserved, unless ref is overridden
var 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
var 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.
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}
return ReactElement(element.type, key, ref, self, source, owner, props);
};
새로운 React element를 반환한다.
key와 ref는 유지된다.
또한, object-assign는 ES6의 assign의 polyfill이며, props의 경우 얕은 복사가 된다.
결론적으로는 다음이 반환된다. createElement와 많이 유사한 로직을 가지고 있다.
<element.type {...element.props} {...props}>{children}</element.type>
isValidElement
/**
* Verifies the object is a ReactElement.
* See https://facebook.github.io/react/docs/top-level-api.html#react.isvalidelement
* @param {?object} object
* @return {boolean} True if `object` is a valid component.
* @final
*/
ReactElement.isValidElement = function (object) {
return typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE;
};
isValidElement는 객체인지, $$typeof 속성이 REACT_ELEMENT_TYPE인지 확인해서 Boolean으로 반환하는 함수이다. 즉, 알맞은 React Element 인지 확인하는 함수이다. (참고 링크)
'Tech > React' 카테고리의 다른 글
react-hot-loader란? (0) | 2021.10.26 |
---|---|
SyntheticEvent and throttle (0) | 2021.08.07 |
Controlled Components, Uncontrolled components (0) | 2021.08.03 |
React Element vs Component (0) | 2021.08.01 |
useEffect 정리 (0) | 2021.07.22 |
댓글