最近在學(xué)習(xí)React的源碼武花,從比較簡單的創(chuàng)建ReactElement開始學(xué)起袄琳,以下是今天要啃的源碼,可能有些地方還不是很深入稚配,相信隨著了解的增多,對react的理解也會更加深入:
ReactElement.createElement = function (type, config, children) {
var propName;
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;
for (propName in config) {
if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
}
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 ("development" !== 'production') {
if (Object.freeze) {
Object.freeze(childArray);
}
}
props.children = childArray;
}
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
if ("development" !== '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);
};
How to trigger it ?
兩種方式可以觸發(fā)上面這個方法:
- 使用JSX創(chuàng)建元素
render() {
return (
<button className="button" type="button")}>Click</button>
)
}
- 使用React.createElement創(chuàng)建元素
render() {
var config = {
className: 'button',
type: 'button'
};
return React.createElement('button', config, 'Click');
}
JSX本質(zhì)上是ReactJS創(chuàng)建元素的語法糖港华,它們都在做同一件事情道川,就是生成一個簡單的按鈕,babel會幫我們將JSX轉(zhuǎn)化成Javascript立宜。
Parameters passed into the method
這個方法看起來是接收三個參數(shù)冒萄,不過了解Javascript函數(shù)機制的話,就知道其實不是的橙数,不過我們先假裝它接收三個參數(shù):
- type: 這個參數(shù)聲明要創(chuàng)建什么類型的DOM元素尊流,分兩種,一種是原生DOM灯帮,比如
div
span
h1
等等崖技,傳入一個string
逻住,比如'div'
'span'
'h1'
等等,即可正確的表示要創(chuàng)建的DOM類型迎献。另一種是自定義的Web Component瞎访,比如MyComponent
,此時需要引入這個組件忿晕,然后將組件作為第一個參數(shù)傳入装诡。
The first part of a JSX tag determines the type of the React element.
Capitalized types indicate that the JSX tag is referring to a React component. These tags get compiled into a direct reference to the named variable, so if you use the JSX <Foo /> expression, Foo must be in scope.(出自ReactJS官方文檔)
- config: 用來聲明組件的屬性列表。
- children: 用來在組件內(nèi)創(chuàng)建子元素践盼,例子中是
Click
鸦采,我們也可以創(chuàng)建更復(fù)雜的子元素,比如div
等等咕幻。
Then, what happened?
- 首先渔伯,在config不為空的情況下,校驗是否有合法的
ref
屬性以及是否是合法的鍵值對肄程。
function hasValidRef(config) {
if ("development" !== 'production') {
if (hasOwnProperty.call(config, 'ref')) {
var getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.ref !== undefined;
}
本菜鳥一直搞不懂問什么要寫"development" !== 'production'
這樣一句永遠為true
的表達式(React源碼中經(jīng)常出現(xiàn))锣吼,歡迎大家評論解答_
首先判斷ref是否是config自身的屬性,而不是從原型鏈上繼承來的屬性蓝厌,然后再判斷此ref
是否有效玄叠。
function hasValidKey(config) {
if ("development" !== 'production') {
if (hasOwnProperty.call(config, 'key')) {
var getter = Object.getOwnPropertyDescriptor(config, 'key').get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.key !== undefined;
}
key
的判斷跟ref
很類似。
裝載self和source
我們的例子中config
的__self
__source
均為undefined
拓提,這兩個變量還沒有搞懂读恃,歡迎大家留言。將
config
中的自有而非從原型鏈繼承得來代态、非保留屬性(key
ref
__self
__source
)存儲到局部變量props
中寺惫。前面提到過假裝只接受三個參數(shù),其實可以接受多于三個參數(shù)的蹦疑,那么第四個西雀、第五個參數(shù)要怎么處理呢?這個方法中歉摧,它們被當(dāng)做子元素來處理
首先判斷子元素的長度是否為1艇肴,是的話直接將第三個參數(shù)放入props.children
中,如果大于1叁温, 那么構(gòu)建子元素數(shù)組豆挽,將第三個、第四個券盅、第五個...參數(shù)依次放入這個數(shù)組中,然后將這個數(shù)組賦值給props.children
膛檀。
將參數(shù)指定的屬性設(shè)置好之后锰镀,開始處理指定元素類型帶有默認值的屬性娘侍,如果被設(shè)置默認值的屬性沒有被指定新值,那么存儲默認值到props
中泳炉。接下來的一段代碼憾筏,在
props
存在key
或ref
的情況下,并且props.$$typeof
為空或者不等于Symbol(react.element)
或者60103
時(即不是ReactElement)花鹅,為props.key
props.ref
添加get
屬性氧腰,get
屬性指向一個函數(shù),這個函數(shù)可以顯示初次的報警信息刨肃。這一步顯然與第1步有聯(lián)系古拴,但仍然想不出在什么情形下會顯示警告,大家知道的話可以告訴我~
下面這段代碼是REACT_ELEMENT_TYPE
的賦值操作
var REACT_ELEMENT_TYPE = typeof Symbol === 'function' && Symbol['for'] && Symbol['for']('react.element') || 0xeac7;
Symbol
是ES6新定義的一種基本數(shù)據(jù)類型:
MDN web docs: Symbol
感覺defineXXXPropWarningGetter
就是為key
ref
屬性添加訪問控制真友,不知道對不對...(原諒React小白留下這么多坑...)黄痪,以下是代碼:
function defineKeyPropWarningGetter(props, displayName) {
var warnAboutAccessingKey = function () {
if (!specialPropKeyWarningShown) {
specialPropKeyWarningShown = true;
"development" !== '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
});
}
function defineRefPropWarningGetter(props, displayName) {
var warnAboutAccessingRef = function () {
if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true;
"development" !== '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
});
}
- 萬事俱備,現(xiàn)在可以正兒八經(jīng)的創(chuàng)建ReactElement啦(構(gòu)建ReactElement數(shù)據(jù)結(jié)構(gòu))
首先給這個element打上react的標簽(REACT_ELEMENT_TYPE
)盔然,然后將校驗合格的變量填充到element
對象中桅打,其中owner是ReactCurrentOwner.current
,就是管理它的component愈案, 并且定義_store.validated
_self
_source
(是否可配置意鲸,是否可編輯,是否可枚舉既荚,值)
var ReactElement = function (type, key, ref, self, source, owner, props) {
var element = {
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
props: props,
_owner: owner
};
if ("development" !== 'production') {
element._store = {};
if (canDefineProperty) {
Object.defineProperty(element._store, 'validated', {
configurable: false,
enumerable: false,
writable: true,
value: false
});
Object.defineProperty(element, '_self', {
configurable: false,
enumerable: false,
writable: false,
value: self
});
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;
};