摘要:在源碼添加的注釋在。解析的主要調用如下當然在下還有些其他的調用。此外,凍結一個對象后該對象的原型也不能被修改。方法封閉一個對象,阻止添加新屬性并將所有現有屬性標記為不可配置。
前言
ReactElement并不像之前所談的PureComponent和Component那樣被頻繁的顯示使用,但我估計他應該是在react暴露出的api中被調用最為頻繁的,關于此看完后面便知。ReactElement中暴露出createElement,createFactory,cloneElement,isValidElement,cloneAndReplaceKey五個方法,總共400來行代碼,比較容易。
文章中如有不當之處,歡迎交流指點。react版本16.8.2。在源碼添加的注釋在githubreact-source-learn。jsx與ReactElement
在使用react時我們經常在render方法返回(函數組件的話可能是直接返回)類似下面的代碼。
測試
這就是傳說中的jsx語法,js并沒有這種東西,這種語法最終都會被轉換成標準的js。請看下圖:
發現這些jsx被轉化成了js,每個組件或者html標簽轉化后都是調用React.createElement(type, config, children)。這里的React.createElement其實就是ReactElement.createElement。由此可以推測,ReactElement暴露的方法是調用最頻繁的。
createElement解析createElement的主要調用如下:
createElement -> ReactElement
當然在dev下還有些其他的調用。
createElement源碼如下
/** * 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主要做了如下事情:
將特殊屬性從config取出, 如key,ref,__self,__source
將非特殊屬性掛到props上,比如上邊那個圖中的className
將第三個及之后的參數掛到props.children上,多個是生成數組,單個是直接掛
默認值defaultProps的處理
將處理好的數據作為參數調用ReactElement并返回
ReactElement源碼如下
// 這個函數做的事非常簡單, 就是將傳進來的參放到一個對象里邊返回 // 其中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,可以找些文檔了解下。
小結下ReactElement.createElement
ReactElement.createElement最終返回的是一個普通的對象,對參數進行了校驗,提取等操作。上面為解析dev下的代碼,去看一下會發現也是比較有趣的。
createFactory,cloneAndReplaceKey,cloneElement和isValidElement createFactoryexport 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. `.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的一個柯里化的操作。
cloneAndReplaceKey// 克隆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屬性替換成新的
isValidElement/** * 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
cloneElement// 和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小結至此,/packages/react.js總的最最重要的東西已經分析完了,關于hooks等其他內容就像不分析了。這里邊的代碼其實并沒有做什神奇的事情,ReactElement只是創建和操作普通對象,Component和PureComponent只是定義了兩個簡單的構造函數,定義了幾個方法,其中比較重要的應該是updater,但是到目前為止還沒有看到他的身影。這些東西都不涉及dom操作,是平臺無關的。
這里代碼都比較好理解,后面就將進入深水區了,要開始研究ReactDOM里邊的render了。加油!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/103429.html
摘要:一語法轉換到語法從轉換到會用到,所以先熟悉下到的轉換。對于庫作者而言,凍結對象可防止有人修改庫的核心對象。 showImg(https://segmentfault.com/img/remote/1460000019757204); 一、JSX語法轉換到Js語法從 JSX 轉換到 JS 會用到React.createElement(),所以先熟悉下 JSX 到 JS 的轉換。 這邊是 ...
摘要:的創建組件,其實根源還是調用了編譯之后一般寫法建議用來進行源碼的跟蹤鏈接從源碼角度來看創建一個組件的過程中發生了什么。 https://github.com/jimwmg/Rea... 1 React.createClass( ) var HelloWorld = React.createClass({ render : function(){ return ...
摘要:一例子看到一個有趣的現象,就是多層嵌套的數組經過后,平鋪成了,接下來以該例解析二作用源碼進行基本的判斷和初始化后,調用該方法就是重命名了,即解析注意,該數組在里面滾了一圈后,會結果三作用的包裹器源碼第一次第二次如果字符串中有連續多個的話 showImg(https://segmentfault.com/img/remote/1460000019968077?w=1240&h=698);...
摘要:本文將對源碼做一個初步解析。首先在方法中校驗參數是否合法,然后調用在中,調用拿到了的一個實例,調用拿到了,用于注入到,和作為返回值,調用開始調度過程在中,首先清理了中的所有子節點,然后了一個并返回是如何調度的是一個什么樣的類的操作是在哪里 初步看了react-dom這個包的一些源碼,發現其比react包要復雜得多,react包中基本不存在跨包調用的情況,他所做的也僅僅是定義了React...
摘要:正式開始系統地學習前端已經三個多月了,感覺前端知識體系龐雜但是又非常有趣。更新一個節點需要做的事情有兩件,更新頂層標簽的屬性,更新這個標簽包裹的子節點。 正式開始系統地學習前端已經三個多月了,感覺前端知識體系龐雜但是又非常有趣。前端演進到現在對開發人員的代碼功底要求已經越來越高,幾年前的前端開發還是大量操作DOM,直接與用戶交互,而React、Vue等MVVM框架的出現,則幫助開發者從...
閱讀 2071·2021-10-12 10:12
閱讀 791·2021-09-24 09:47
閱讀 1191·2021-08-19 11:12
閱讀 3468·2019-08-29 13:06
閱讀 689·2019-08-26 11:43
閱讀 2570·2019-08-23 17:20
閱讀 1153·2019-08-23 16:52
閱讀 2601·2019-08-23 14:27