ReactElement並不像以前所談的PureComponent和Component那樣被頻繁的顯示使用,但我估計他應該是在react暴露出的api中被調用最爲頻繁的,關於此看完後面便知。ReactElement中暴露出createElement,createFactory,cloneElement,isValidElement,cloneAndReplaceKey五個方法,總共400來行代碼,比較容易。javascript
文章中若有不當之處,歡迎交流指點。react版本
16.8.2
。在源碼添加的註釋在github
react-source-learn。
在使用react時咱們常常在render方法返回(函數組件的話多是直接返回)相似下面的代碼。html
<Wrap> <h1>測試</h1> <List /> <Footer /> </Wrap>
這就是傳說中的jsx語法,js並無這種東西,這種語法最終都會被轉換成標準的js。請看下圖:java
發現這些jsx被轉化成了js,每一個組件或者html標籤轉化後都是調用React.createElement(type, config, children)。這裏的React.createElement其實就是ReactElement.createElement。由此能夠推測,ReactElement暴露的方法是調用最頻繁的。react
createElement的主要調用以下:git
createElement -> ReactElement
固然在dev下還有些其餘的調用。github
createElement源碼以下api
/** * Create and return a new ReactElement of the given type. * See https://reactjs.org/docs/react-api.html#createelement */ // jsx轉換後調用的方法 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 // 將config中的數據放到props中, key,ref,__self,__source除外 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. // children生成 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 // 複製默認props if (type && type.defaultProps) { const defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } // 這裏否則從props中讀key, 和ref, 可是裏邊事實上就是沒有的 if (__DEV__) { if (key || ref) { const displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type; if (key) { // displayName: 構造函數名, 或標籤名 a , h1 defineKeyPropWarningGetter(props, displayName); } if (ref) { defineRefPropWarningGetter(props, displayName); } } } // 就一個普通對象 return ReactElement( type, key, ref, self, source, ReactCurrentOwner.current, props, ); }
createElement主要作了以下事情:數組
ReactElement源碼以下less
// 這個函數作的事很是簡單, 就是將傳進來的參放到一個對象裏邊返回 // 其中source, self在生產模式沒有返回 // owner 變成了_owner // 開發模式下, 返回了將source, self也掛在了返回的對象上, 變成了_source, _self 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, // 一個Symobol或者16進制數, //用於表示ReactElement類型 // 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, }; // 這裏邊放了self, source 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, }); // Object.freeze() 方法能夠凍結一個對象。一個被凍結的對象不再能被修改; // 凍結了一個對象則不能向這個對象添加新的屬性,不能刪除已有屬性,不能修改該對象已有屬性的可枚舉性、 // 可配置性、可寫性,以及不能修改已有屬性的值。 // 此外,凍結一個對象後該對象的原型也不能被修改。freeze() 返回和傳入的參數相同的對象。 // Object.seal()方法封閉一個對象,阻止添加新屬性並將全部現有屬性標記爲不可配置。當前屬性的值只要可寫就能夠改變。 // Object.preventExtensions()方法讓一個對象變的不可擴展,也就是永遠不能再添加新的屬性。 if (Object.freeze) { Object.freeze(element.props); Object.freeze(element); } } return element; };
他幾乎是沒作什麼事情的,就是將傳入的參數放到一個對象返回,加了一個$$typeof標識ReactElement。其中使用了一個Object.freeze方法,這個方法不太經常使用,意思是凍結一個對象,使其不能被修改,相關的還有Object.seal,Object.preventExtensions,能夠找些文檔瞭解下。dom
小結下ReactElement.createElement
ReactElement.createElement最終返回的是一個普通的對象,對參數進行了校驗,提取等操做。上面爲解析dev下的代碼,去看一下會發現也是比較有趣的。
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; // 這樣 // return function factory(...args) { // return createElement(type, ...args); // } }
這個方法很簡單,是對createElement的一個柯里化的操做。
// 克隆reactElement並將key改成新key export function cloneAndReplaceKey(oldElement, newKey) { const newElement = ReactElement( oldElement.type, newKey, oldElement.ref, oldElement._self, oldElement._source, oldElement._owner, oldElement.props, ); return newElement; }
從舊的ReactElement對象生成一個新的,將key屬性替換成新的
/** * Verifies the object is a ReactElement. * See https://reactjs.org/docs/react-api.html#isvalidelement * @param {?object} object * @return {boolean} True if `object` is a ReactElement. * @final */ export function isValidElement(object) { // 仍是很嚴謹的 return ( typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE ); }
判斷一個值是否是ReactElement,使用了建立時掛上去的$$typeof
// 和createElement基本相同 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); }
這段代碼和createElement很是類似,不一樣之處在於他是返回第一個參數ReactElement的一個副本。他的key,ref等屬性和提供的須要被克隆的ReactElement的相同,props也是原來的props,可是能夠傳入config修改。
至此,/packages/react.js總的最最重要的東西已經分析完了,關於hooks等其餘內容就像不分析了。這裏邊的代碼其實並無作什神奇的事情,ReactElement只是建立和操做普通對象,Component和PureComponent只是定義了兩個簡單的構造函數,定義了幾個方法,其中比較重要的應該是updater,可是到目前爲止尚未看到他的身影。這些東西都不涉及dom操做,是平臺無關的。
這裏代碼都比較好理解,後面就將進入深水區了,要開始研究ReactDOM裏邊的render了。加油!