React原始碼學習——ReactElement.createElement
最近在學習React的原始碼,從比較簡單的建立ReactElement開始學起,以下是今天要啃的原始碼,可能有些地方還不是很深入,相信隨著瞭解的增多,對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 ?
兩種方式可以觸發上面這個方法:
- 使用JSX建立元素
render() {
return (
<button className="button" type="button")}>Click</button>
)
}
- 使用React.createElement建立元素
render() {
var config = {
className: 'button',
type: 'button'
};
return React.createElement('button', config, 'Click');
}
JSX本質上是ReactJS建立元素的語法糖,它們都在做同一件事情,就是生成一個簡單的按鈕,babel會幫我們將JSX轉化成Javascript。
Parameters passed into the method
這個方法看起來是接收三個引數,不過了解Javascript函式機制的話,就知道其實不是的,不過我們先假裝它接收三個引數:
- type: 這個引數宣告要建立什麼型別的DOM元素,分兩種,一種是原生DOM,比如
div
span
h1
等等,傳入一個string
,比如'div'
'span'
'h1'
等等,即可正確的表示要建立的DOM型別。另一種是自定義的Web Component,比如MyComponent
,此時需要引入這個元件,然後將元件作為第一個引數傳入。
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: 用來在元件內建立子元素,例子中是
Click
,我們也可以建立更復雜的子元素,比如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原始碼中經常出現),歡迎大家評論解答_
首先判斷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
中。前面提到過假裝只接受三個引數,其實可以接受多於三個引數的,那麼第四個、第五個引數要怎麼處理呢?這個方法中,它們被當做子元素來處理
首先判斷子元素的長度是否為1,是的話直接將第三個引數放入props.children
中,如果大於1, 那麼構建子元素陣列,將第三個、第四個、第五個...引數依次放入這個陣列中,然後將這個陣列賦值給props.children
。
將引數指定的屬性設定好之後,開始處理指定元素型別帶有預設值的屬性,如果被設定預設值的屬性沒有被指定新值,那麼儲存預設值到props
中。接下來的一段程式碼,在
props
存在key
或ref
的情況下,並且props.$$typeof
為空或者不等於Symbol(react.element)
或者60103
時(即不是ReactElement),為props.key
props.ref
新增get
屬性,get
屬性指向一個函式,這個函式可以顯示初次的報警資訊。這一步顯然與第1步有聯絡,但仍然想不出在什麼情形下會顯示警告,大家知道的話可以告訴我~
下面這段程式碼是REACT_ELEMENT_TYPE
的賦值操作
var REACT_ELEMENT_TYPE = typeof Symbol === 'function' && Symbol['for'] && Symbol['for']('react.element') || 0xeac7;
Symbol
是ES6新定義的一種基本資料型別:
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
});
}
- 萬事俱備,現在可以正兒八經的建立ReactElement啦(構建ReactElement資料結構)
首先給這個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;
};
相關文章
- React 原始碼學習(八):元件更新React原始碼元件
- React 原始碼學習(一):HTML 元素渲染React原始碼HTML
- React 原始碼學習(五):事件機制React原始碼事件
- 原始碼學習原始碼
- fishhook原始碼學習Hook原始碼
- MMKV原始碼學習原始碼
- vue原始碼學習Vue原始碼
- 【原始碼學習】ThreadLocal原始碼thread
- EventBus原始碼學習原始碼
- ObjectMapper原始碼學習ObjectAPP原始碼
- express原始碼學習Express原始碼
- go原始碼學習Go原始碼
- 學習HashMap原始碼HashMap原始碼
- Java容器原始碼學習--ArrayList原始碼分析Java原始碼
- React 原始碼分析React原始碼
- React原始碼分析React原始碼
- React原始碼解析React原始碼
- Vue 原始碼學習(一)Vue原始碼
- Okio 框架原始碼學習框架原始碼
- java原始碼學習-SpliteratorJava原始碼
- jQuery原始碼學習之$()jQuery原始碼
- vue observer 原始碼學習VueServer原始碼
- 來聊聊原始碼學習原始碼
- 原始碼學習之EllipsizingTextView原始碼TextView
- EOS原始碼學習系列原始碼
- 精讀《原始碼學習》原始碼
- PHP 原始碼加密學習PHP原始碼加密
- Mybatis 原始碼學習(二)MyBatis原始碼
- 學習RadonDB原始碼(二)原始碼
- 學習RadonDB原始碼(一)原始碼
- Masonry 原始碼學習整理原始碼
- VeraCrypt原始碼學習-序原始碼
- java原始碼學習-AbstractSequentialListJava原始碼
- 【菜鳥讀原始碼】halo✍原始碼學習 (一)原始碼
- React學習React
- 學習ReactReact
- 深度學習03-sklearn.LinearRegression 原始碼學習深度學習原始碼
- React原始碼解析(二):react-elementReact原始碼
- React原始碼解析(三):react-componentReact原始碼