摘要:在源碼添加的注釋在。解析的主要調(diào)用如下當(dāng)然在下還有些其他的調(diào)用。此外,凍結(jié)一個對象后該對象的原型也不能被修改。方法封閉一個對象,阻止添加新屬性并將所有現(xiàn)有屬性標(biāo)記為不可配置。
前言
ReactElement并不像之前所談的PureComponent和Component那樣被頻繁的顯示使用,但我估計他應(yīng)該是在react暴露出的api中被調(diào)用最為頻繁的,關(guān)于此看完后面便知。ReactElement中暴露出createElement,createFactory,cloneElement,isValidElement,cloneAndReplaceKey五個方法,總共400來行代碼,比較容易。
文章中如有不當(dāng)之處,歡迎交流指點。react版本16.8.2。在源碼添加的注釋在githubreact-source-learn。jsx與ReactElement
在使用react時我們經(jīng)常在render方法返回(函數(shù)組件的話可能是直接返回)類似下面的代碼。
測試
這就是傳說中的jsx語法,js并沒有這種東西,這種語法最終都會被轉(zhuǎn)換成標(biāo)準(zhǔn)的js。請看下圖:
發(fā)現(xiàn)這些jsx被轉(zhuǎn)化成了js,每個組件或者h(yuǎn)tml標(biāo)簽轉(zhuǎn)化后都是調(diào)用React.createElement(type, config, children)。這里的React.createElement其實就是ReactElement.createElement。由此可以推測,ReactElement暴露的方法是調(diào)用最頻繁的。
createElement解析createElement的主要調(diào)用如下:
createElement -> ReactElement
當(dāng)然在dev下還有些其他的調(diào)用。
createElement源碼如下
/**
* Create and return a new ReactElement of the given type.
* See https://reactjs.org/docs/react-api.html#createelement
*/
// jsx轉(zhuǎn)換后調(diào)用的方法
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中的數(shù)據(jù)放到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
// 復(fù)制默認(rèn)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: 構(gòu)造函數(shù)名, 或標(biāo)簽名 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
將第三個及之后的參數(shù)掛到props.children上,多個是生成數(shù)組,單個是直接掛
默認(rèn)值defaultProps的處理
將處理好的數(shù)據(jù)作為參數(shù)調(diào)用ReactElement并返回
ReactElement源碼如下
// 這個函數(shù)做的事非常簡單, 就是將傳進(jìn)來的參放到一個對象里邊返回
// 其中source, self在生產(chǎn)模式?jīng)]有返回
// owner 變成了_owner
// 開發(fā)模式下, 返回了將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進(jìn)制數(shù),
//用于表示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() 方法可以凍結(jié)一個對象。一個被凍結(jié)的對象再也不能被修改;
// 凍結(jié)了一個對象則不能向這個對象添加新的屬性,不能刪除已有屬性,不能修改該對象已有屬性的可枚舉性、
// 可配置性、可寫性,以及不能修改已有屬性的值。
// 此外,凍結(jié)一個對象后該對象的原型也不能被修改。freeze() 返回和傳入的參數(shù)相同的對象。
// Object.seal()方法封閉一個對象,阻止添加新屬性并將所有現(xiàn)有屬性標(biāo)記為不可配置。當(dāng)前屬性的值只要可寫就可以改變。
// Object.preventExtensions()方法讓一個對象變的不可擴展,也就是永遠(yuǎn)不能再添加新的屬性。
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}
return element;
};
他幾乎是沒做什么事情的,就是將傳入的參數(shù)放到一個對象返回,加了一個$$typeof標(biāo)識ReactElement。其中使用了一個Object.freeze方法,這個方法不太常用,意思是凍結(jié)一個對象,使其不能被修改,相關(guān)的還有Object.seal,Object.preventExtensions,可以找些文檔了解下。
小結(jié)下ReactElement.createElement
ReactElement.createElement最終返回的是一個普通的對象,對參數(shù)進(jìn)行了校驗,提取等操作。上面為解析dev下的代碼,去看一下會發(fā)現(xiàn)也是比較有趣的。
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) {
// 還是很嚴(yán)謹(jǐn)?shù)? return (
typeof object === "object" &&
object !== null &&
object.$$typeof === REACT_ELEMENT_TYPE
);
}
判斷一個值是不是ReactElement,使用了創(chuàng)建時掛上去的$$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非常相似,不同之處在于他是返回第一個參數(shù)ReactElement的一個副本。他的key,ref等屬性和提供的需要被克隆的ReactElement的相同,props也是原來的props,但是可以傳入config修改。
/packages/react.js小結(jié)至此,/packages/react.js總的最最重要的東西已經(jīng)分析完了,關(guān)于hooks等其他內(nèi)容就像不分析了。這里邊的代碼其實并沒有做什神奇的事情,ReactElement只是創(chuàng)建和操作普通對象,Component和PureComponent只是定義了兩個簡單的構(gòu)造函數(shù),定義了幾個方法,其中比較重要的應(yīng)該是updater,但是到目前為止還沒有看到他的身影。這些東西都不涉及dom操作,是平臺無關(guān)的。
這里代碼都比較好理解,后面就將進(jìn)入深水區(qū)了,要開始研究ReactDOM里邊的render了。加油!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://hztianpu.com/yun/103429.html
摘要:一語法轉(zhuǎn)換到語法從轉(zhuǎn)換到會用到,所以先熟悉下到的轉(zhuǎn)換。對于庫作者而言,凍結(jié)對象可防止有人修改庫的核心對象。 showImg(https://segmentfault.com/img/remote/1460000019757204); 一、JSX語法轉(zhuǎn)換到Js語法從 JSX 轉(zhuǎn)換到 JS 會用到React.createElement(),所以先熟悉下 JSX 到 JS 的轉(zhuǎn)換。 這邊是 ...
摘要:的創(chuàng)建組件,其實根源還是調(diào)用了編譯之后一般寫法建議用來進(jìn)行源碼的跟蹤鏈接從源碼角度來看創(chuàng)建一個組件的過程中發(fā)生了什么。 https://github.com/jimwmg/Rea... 1 React.createClass( ) var HelloWorld = React.createClass({ render : function(){ return ...
摘要:一例子看到一個有趣的現(xiàn)象,就是多層嵌套的數(shù)組經(jīng)過后,平鋪成了,接下來以該例解析二作用源碼進(jìn)行基本的判斷和初始化后,調(diào)用該方法就是重命名了,即解析注意,該數(shù)組在里面滾了一圈后,會結(jié)果三作用的包裹器源碼第一次第二次如果字符串中有連續(xù)多個的話 showImg(https://segmentfault.com/img/remote/1460000019968077?w=1240&h=698);...
摘要:本文將對源碼做一個初步解析。首先在方法中校驗參數(shù)是否合法,然后調(diào)用在中,調(diào)用拿到了的一個實例,調(diào)用拿到了,用于注入到,和作為返回值,調(diào)用開始調(diào)度過程在中,首先清理了中的所有子節(jié)點,然后了一個并返回是如何調(diào)度的是一個什么樣的類的操作是在哪里 初步看了react-dom這個包的一些源碼,發(fā)現(xiàn)其比react包要復(fù)雜得多,react包中基本不存在跨包調(diào)用的情況,他所做的也僅僅是定義了React...
摘要:正式開始系統(tǒng)地學(xué)習(xí)前端已經(jīng)三個多月了,感覺前端知識體系龐雜但是又非常有趣。更新一個節(jié)點需要做的事情有兩件,更新頂層標(biāo)簽的屬性,更新這個標(biāo)簽包裹的子節(jié)點。 正式開始系統(tǒng)地學(xué)習(xí)前端已經(jīng)三個多月了,感覺前端知識體系龐雜但是又非常有趣。前端演進(jìn)到現(xiàn)在對開發(fā)人員的代碼功底要求已經(jīng)越來越高,幾年前的前端開發(fā)還是大量操作DOM,直接與用戶交互,而React、Vue等MVVM框架的出現(xiàn),則幫助開發(fā)者從...
閱讀 2124·2021-10-12 10:12
閱讀 832·2021-09-24 09:47
閱讀 1246·2021-08-19 11:12
閱讀 3539·2019-08-29 13:06
閱讀 747·2019-08-26 11:43
閱讀 2642·2019-08-23 17:20
閱讀 1201·2019-08-23 16:52
閱讀 2664·2019-08-23 14:27