React.createElement
在React中,JSX語法糖都會被轉換爲React.createElement
的形式。
例如:
<div id='div'>hello</div>
會被轉換爲:
React.createElement('div', {id: 'div'}, 'hello');
這個方法是React對象的一個方法,在源碼目錄下的React.js
中,可以看見React對象內包含了這個方法。
源碼
而這個方法真正的定義則是在ReactElement.js
文件內。
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 (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,
);
}
首先方法內定義了一個props
對象,用以存放我們爲組件傳入的props。需要注意的是,config參數並不一定就直接是最終的props,因爲key
和ref
都不是傳給組件的。後面定義的key
和ref
變量就是用來存放這兩個值的。
接下來
if (hasValidRef(config)) {
ref = config.ref;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
這兩個if
分別判斷傳入的config
參數內包不包含合法的ref
和合法的key
,若包含則分別用ref
變量、key
變量保存下來。
然後
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
這段代碼是負責將config
參數中除去key
、ref
的值放入之前定義的props
對象中。hasOwnProperty
用以確認當前的鍵是定義在config
上而不是其原型鏈上。
!RESERVED_PROPS.hasOwnProperty(propName)
則是排除掉不屬於props
對象的屬性。RESERVED_PROPS
的定義如下:
const RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true,
};
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;
}
從JSX轉換成JavaScript之後的代碼可以看出,React.createElement
方法從第三個參數開始,之後所有的參數都是該組件的子組件。
所以arguments.length-2
就是在計算子組件的個數。若只有一個子組件,那麼props.children
的值就是這個子組件。若大於一個,則將所有子組件都放進一個數組內。
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
我們在編寫組件的時候可以通過編寫defaultProps
來爲組件設置缺省的props
。而這個缺省的props
就是在這裏被設置的。
可以看出,先判斷有沒有defaultProps
,若有接着判斷defaultProps
中的每一個鍵,若該鍵在之前定義的props
對象中找不到對應的值,則將defaultProps
中的對應值賦給props
對象。這樣就實現了“若傳入的props
有對應值就是用傳入的,若沒有則使用默認的”的效果了。
最後就是返回ReactElement
方法返回的結果。
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;
};
一眼就可以看出,ReactElement
方法返回的就是一個普通的JS對象。這個對象內的值基本就是剛剛React.createElement
中傳入的那些。
這裏面要注意的是$$typeof
這個屬性,它用來標記該對象是一個React
元素。