摘要:僅針對數據屬性描述有效獲取該屬性的訪問器函數。當且僅當指定對象的屬性可以被枚舉出時,為。凍結及其對象主要目的是為提高測試環境下效率,將的一些屬性配置為不可枚舉,進行遍歷的時候跳過這些屬性。
React系列
React系列 --- 簡單模擬語法(一)
React系列 --- Jsx, 合成事件與Refs(二)
React系列 --- virtualdom diff算法實現分析(三)
React系列 --- 從Mixin到HOC再到HOOKS(四)
React系列 --- createElement, ReactElement與Component部分源碼解析(五)
React系列 --- 從使用React了解Css的各種使用方案(六)
因為之前寫過一些文章分別關于怎么模擬React語法,React基本知識和virtualdom diff實現思路,接下來就跟著React源碼大概了解一下怎么一個過程,只是主邏輯代碼,忽略部分開發環境的代碼.
以下解析僅限于我當時理解,不一定準確.
還是用之前的例子
123456
編譯成
React.createElement("div", { className: "num", index: 1 }, React.createElement("span", null, "123456"));createElement
我們看下API的語法
React.createElement( type, [props], [...children] )
創建并返回給定類型的新 React element 。
參數 | 描述 |
---|---|
type | 既可以是一個標簽名稱字符串,也可以是一個 React component 類型(一個類或一個函數),或者一個React fragment 類型 |
props | 各種屬性值 |
children | 子元素 |
因為有babel會編譯JSX,所以一般很少會直接調用這個方法.
然后我們進入找到對應的源碼位置查看代碼 react/packages/react/src/ReactElement.js
/** * Create and return a new ReactElement of the given type. * See https://reactjs.org/docs/react-api.html#createelement */ 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; // 有傳config的情況下 if (config != null) { // 是否有有效的Ref if (hasValidRef(config)) { ref = config.ref; } // 是否有有效的Key if (hasValidKey(config)) { key = "" + config.key; } // 暫時還沒聯系上下文,保存self和source self = config.__self === undefined ? null : config.__self; source = config.__source === undefined ? null : config.__source; // Remaining properties are added to a new props object 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. 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 if (type && type.defaultProps) { const defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } if (__DEV__) { if (key || ref) { // 如果type是函數說明不是原生dom,所以可以取一下幾個值 const displayName = typeof type === "function" ? type.displayName || type.name || "Unknown" : type; // 定義key屬性的取值器,添加對應警告 if (key) { defineKeyPropWarningGetter(props, displayName); } // 定義ref屬性的取值器,添加對應警告 if (ref) { defineRefPropWarningGetter(props, displayName); } } } return ReactElement( type, key, ref, self, source, ReactCurrentOwner.current, props, ); }
代碼還比較簡單,可以看出就是傳入參數之后它會幫你做些特殊處理然后導出給ReactElement方法使用,如果有部分代碼還不知道是干嘛的話也不用擔心,下面會有說到
function hasValidRef(config) { // 開發環境下 if (__DEV__) { // 自身是否含有ref字段 if (hasOwnProperty.call(config, "ref")) { // 獲取它的取值器 const getter = Object.getOwnPropertyDescriptor(config, "ref").get; // 滿足條件的話為非法ref if (getter && getter.isReactWarning) { return false; } } } // 直接和undefined作比較判斷是否合法 return config.ref !== undefined; } 同上 function hasValidKey(config) { // 開發環境 if (__DEV__) { if (hasOwnProperty.call(config, "key")) { const getter = Object.getOwnPropertyDescriptor(config, "key").get; if (getter && getter.isReactWarning) { return false; } } } return config.key !== undefined; }
// 初始化標記 let specialPropKeyWarningShown, specialPropRefWarningShown; // 定義key的取值器 function defineKeyPropWarningGetter(props, displayName) { const warnAboutAccessingKey = function() { if (!specialPropKeyWarningShown) { specialPropKeyWarningShown = true; // 目測是警告提示 warningWithoutStack( 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, ); } }; // 是否已經警告過 warnAboutAccessingKey.isReactWarning = true; // 定義key字段 Object.defineProperty(props, "key", { get: warnAboutAccessingKey, configurable: true, }); } // 同上 function defineRefPropWarningGetter(props, displayName) { const warnAboutAccessingRef = function() { if (!specialPropRefWarningShown) { specialPropRefWarningShown = true; warningWithoutStack( 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, ); } }; warnAboutAccessingRef.isReactWarning = true; Object.defineProperty(props, "ref", { get: warnAboutAccessingRef, configurable: true, }); }
代碼來看是開發模式下限制了對應key和ref的取值器,使用時會執行對應方法進行報錯不讓讀取.
至此相關源碼基本了解了
getOwnPropertyDescriptor上面其中核心方法介紹是這個
Object.getOwnPropertyDescriptor(obj, prop)
方法返回指定對象上一個自有屬性對應的屬性描述符。(自有屬性指的是直接賦予該對象的屬性,不需要從原型鏈上進行查找的屬性)
參數 | 描述 |
---|---|
obj | 需要查找的目標對象 |
prop | 目標對象內屬性名稱 |
返回值 | 如果指定的屬性存在于對象上,則返回其屬性描述符對象(property descriptor),否則返回 undefined |
該方法允許對一個屬性的描述進行檢索。在 Javascript 中, 屬性 由一個字符串類型的“名字”(name)和一個“屬性描述符”(property descriptor)對象構成。
一個屬性描述符是一個記錄,由下面屬性當中的某些組成的:
屬性 | 描述 |
---|---|
value | 該屬性的值(僅針對數據屬性描述符有效) |
writable | 當且僅當屬性的值可以被改變時為true。(僅針對數據屬性描述有效) |
get | 獲取該屬性的訪問器函數(getter)。如果沒有訪問器, 該值為undefined。(僅針對包含訪問器或設置器的屬性描述有效) |
set | 獲取該屬性的設置器函數(setter)。 如果沒有設置器, 該值為undefined。(僅針對包含訪問器或設置器的屬性描述有效) |
configurable | 當且僅當指定對象的屬性描述可以被改變或者屬性可被刪除時,為true。 |
enumerable | 當且僅當指定對象的屬性可以被枚舉出時,為 true。 |
然后再看看ReactElement的源碼
/** * Factory method to create a new React element. This no longer adheres to * the class pattern, so do not use new to call it. Also, no instanceof check * will work. Instead test $$typeof field against Symbol.for("react.element") to check * if something is a React Element. * * @param {*} type * @param {*} props * @param {*} key * @param {string|object} ref * @param {*} owner * @param {*} self A *temporary* helper to detect places where `this` is * different from the `owner` when React.createElement is called, so that we * can warn. We want to get rid of owner and replace string `ref`s with arrow * functions, and as long as `this` and owner are the same, there will be no * change in behavior. * @param {*} source An annotation object (added by a transpiler or otherwise) * indicating filename, line number, and/or other information. * @internal */ 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, // 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, }; // 開發模式下增改部分屬性 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, }); if (Object.freeze) { Object.freeze(element.props); Object.freeze(element); } } return element; };
整段代碼來看它是在開發環境下對字段作處理:
創建React元素,設置對應屬性值
開發環境下
創建_store屬性并配置其validated的屬性描述符對象,達到方便調試React元素的目的
配置_self的屬性描述符對象,self和source只是DEV的屬性
配置_source的屬性描述符對象,出于測試的目的,應該將在兩個不同位置創建的兩個元素視為相等的,因此我們將它從枚舉中隱藏起來。
凍結element及其props對象
主要目的是為提高測試環境下效率,將element的一些屬性配置為不可枚舉,進行遍歷的時候跳過這些屬性。
其中REACT_ELEMENT_TYPE是
// The Symbol used to tag the ReactElement type. If there is no native Symbol // nor polyfill, then a plain number is used for performance. var REACT_ELEMENT_TYPE = (typeof Symbol === "function" && Symbol.for && Symbol.for("react.element")) || 0xeac7;
用來標識這個對象是一個ReactElement對象,至此Jsx編譯成ReactElement對象的相關源碼大概知道了.
React組件創建 createClass16.0.0以后已經廢棄,可忽略
ES6類繼承從官方例子來看React.Component
class Welcome extends React.Component { render() { returnHello, {this.props.name}
; } }
從源碼看這個類寫法做了什么 react/packages/react/src/ReactBaseClasses.js
const emptyObject = {}; if (__DEV__) { Object.freeze(emptyObject); } /** * Base class helpers for the updating state of a component. */ function Component(props, context, updater) { this.props = props; this.context = context; // If a component has string refs, we will assign a different object later. this.refs = emptyObject; // We initialize the default updater but the real one gets injected by the // renderer. this.updater = updater || ReactNoopUpdateQueue; } Component.prototype.isReactComponent = {};
結構比較簡單,結合注釋可以知道只是基本賦值,里面有個更新器后面再說,現在先記住是用來更新State就行了.
/** * Sets a subset of the state. Always use this to mutate * state. You should treat `this.state` as immutable. * * There is no guarantee that `this.state` will be immediately updated, so * accessing `this.state` after calling this method may return the old value. * * There is no guarantee that calls to `setState` will run synchronously, * as they may eventually be batched together. You can provide an optional * callback that will be executed when the call to setState is actually * completed. * * When a function is provided to setState, it will be called at some point in * the future (not synchronously). It will be called with the up to date * component arguments (state, props, context). These values can be different * from this.* because your function may be called after receiveProps but before * shouldComponentUpdate, and this new state, props, and context will not yet be * assigned to this. * * @param {object|function} partialState Next partial state or function to * produce next partial state to be merged with current state. * @param {?function} callback Called after state is updated. * @final * @protected */ Component.prototype.setState = function(partialState, callback) { invariant( typeof partialState === "object" || typeof partialState === "function" || partialState == null, "setState(...): takes an object of state variables to update or a " + "function which returns an object of state variables.", ); this.updater.enqueueSetState(this, partialState, callback, "setState"); };
注釋很長實際代碼很短,大概意思就是
不保證this.state會立即更新,所以調用方法之后可能獲取的舊數據
不保證this.state會同步運行,可能最終他們會批量組合執行,可以提供一個可選完成回調當更新之后再執行
回調會在未來某個點執行,可以拿到最新的入參(state, props, context),不同于this.XX,因為它會在shouldComponentUpdate之前接收新的props屬性之后執行,此時還沒賦值給this.
/** * Forces an update. This should only be invoked when it is known with * certainty that we are **not** in a DOM transaction. * * You may want to call this when you know that some deeper aspect of the * component"s state has changed but `setState` was not called. * * This will not invoke `shouldComponentUpdate`, but it will invoke * `componentWillUpdate` and `componentDidUpdate`. * * @param {?function} callback Called after update is complete. * @final * @protected */ Component.prototype.forceUpdate = function(callback) { this.updater.enqueueForceUpdate(this, callback, "forceUpdate"); };
這是強制更新視圖的方法
這應該只在確定我們不是在一個DOM事務中時調用
這應該在當你知道某些深層嵌套組件狀態已變但是沒有執行setState的時候調用
它不會執行shouldComponentUpdate但會執行componentWillUpdate 和componentDidUpdate生命周期
接著我們看源碼 react/packages/shared/invariant.js做了什么
/** * Use invariant() to assert state which your program assumes to be true. * * Provide sprintf-style format (only %s is supported) and arguments * to provide information about what broke and what you were * expecting. * * The invariant message will be stripped in production, but the invariant * will remain to ensure logic does not differ in production. */ export default function invariant(condition, format, a, b, c, d, e, f) { throw new Error( "Internal React error: invariant() is meant to be replaced at compile " + "time. There is no runtime version.", ); }
使用constant()斷言程序假定為真的狀態,僅用于開發,會從生產環境中剝離保證不受影響
接下來我們再看看 react/packages/react/src/ReactNoopUpdateQueue.js
/** * This is the abstract API for an update queue. */ const ReactNoopUpdateQueue = { /** * Checks whether or not this composite component is mounted. * @param {ReactClass} publicInstance The instance we want to test. * @return {boolean} True if mounted, false otherwise. * @protected * @final */ isMounted: function(publicInstance) { return false; }, /** * Forces an update. This should only be invoked when it is known with * certainty that we are **not** in a DOM transaction. * * You may want to call this when you know that some deeper aspect of the * component"s state has changed but `setState` was not called. * * This will not invoke `shouldComponentUpdate`, but it will invoke * `componentWillUpdate` and `componentDidUpdate`. * * @param {ReactClass} publicInstance The instance that should rerender. * @param {?function} callback Called after component is updated. * @param {?string} callerName name of the calling function in the public API. * @internal */ enqueueForceUpdate: function(publicInstance, callback, callerName) { warnNoop(publicInstance, "forceUpdate"); }, /** * Replaces all of the state. Always use this or `setState` to mutate state. * You should treat `this.state` as immutable. * * There is no guarantee that `this.state` will be immediately updated, so * accessing `this.state` after calling this method may return the old value. * * @param {ReactClass} publicInstance The instance that should rerender. * @param {object} completeState Next state. * @param {?function} callback Called after component is updated. * @param {?string} callerName name of the calling function in the public API. * @internal */ enqueueReplaceState: function( publicInstance, completeState, callback, callerName, ) { warnNoop(publicInstance, "replaceState"); }, /** * Sets a subset of the state. This only exists because _pendingState is * internal. This provides a merging strategy that is not available to deep * properties which is confusing. TODO: Expose pendingState or don"t use it * during the merge. * * @param {ReactClass} publicInstance The instance that should rerender. * @param {object} partialState Next partial state to be merged with state. * @param {?function} callback Called after component is updated. * @param {?string} Name of the calling function in the public API. * @internal */ enqueueSetState: function( publicInstance, partialState, callback, callerName, ) { warnNoop(publicInstance, "setState"); }, }; export default ReactNoopUpdateQueue;
里面提供了三個函數,分別是
enqueueForceUpdate: 強制更新隊列包裝器
enqueueReplaceState: 狀態替換隊列包裝器
enqueueSetState: 狀態更新隊列包裝器
實際里面都是調用同一個方法warnNoop,設置首參數都一樣.
const didWarnStateUpdateForUnmountedComponent = {}; function warnNoop(publicInstance, callerName) { if (__DEV__) { const constructor = publicInstance.constructor; const componentName = (constructor && (constructor.displayName || constructor.name)) || "ReactClass"; const warningKey = `${componentName}.${callerName}`; if (didWarnStateUpdateForUnmountedComponent[warningKey]) { return; } warningWithoutStack( false, "Can"t call %s on a component that is not yet mounted. " + "This is a no-op, but it might indicate a bug in your application. " + "Instead, assign to `this.state` directly or define a `state = {};` " + "class property with the desired state in the %s component.", callerName, componentName, ); didWarnStateUpdateForUnmountedComponent[warningKey] = true; } }
目測應該是給傳入的React組件實例設置componentName和KEY, 在里面我們再次看到同一個方法warningWithoutStack,我們直接看看他究竟做了什么. react/packages/shared/warningWithoutStack.js
/** * Similar to invariant but only logs a warning if the condition is not met. * This can be used to log issues in development environments in critical * paths. Removing the logging code for production environments will keep the * same logic and follow the same code paths. */ let warningWithoutStack = () => {}; if (__DEV__) { warningWithoutStack = function(condition, format, ...args) { if (format === undefined) { throw new Error( "`warningWithoutStack(condition, format, ...args)` requires a warning " + "message argument", ); } if (args.length > 8) { // Check before the condition to catch violations early. throw new Error( "warningWithoutStack() currently supports at most 8 arguments.", ); } if (condition) { return; } if (typeof console !== "undefined") { const argsWithFormat = args.map(item => "" + item); argsWithFormat.unshift("Warning: " + format); // We intentionally don"t use spread (or .apply) directly because it // breaks IE9: https://github.com/facebook/react/issues/13610 Function.prototype.apply.call(console.error, console, argsWithFormat); } try { // --- Welcome to debugging React --- // This error was thrown as a convenience so that you can use this stack // to find the callsite that caused this warning to fire. let argIndex = 0; const message = "Warning: " + format.replace(/%s/g, () => args[argIndex++]); throw new Error(message); } catch (x) {} }; } export default warningWithoutStack;
類似invariant但是只有不滿足條件的時候才會打印出警告.這可以用于在關鍵路徑中記錄開發環境中的問題.生產環境下會移除日志代碼保證正常邏輯.代碼只是一些基本的條件設定和優雅降級代碼.
還有一個類似的繼承類PureComponent,可以用于組件進行淺對比決定是否需要更新
function ComponentDummy() {} ComponentDummy.prototype = Component.prototype; /** * Convenience component with default shallow equality check for sCU. */ function PureComponent(props, context, updater) { this.props = props; this.context = context; // If a component has string refs, we will assign a different object later. this.refs = emptyObject; this.updater = updater || ReactNoopUpdateQueue; } const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy()); pureComponentPrototype.constructor = PureComponent; // Avoid an extra prototype jump for these methods. Object.assign(pureComponentPrototype, Component.prototype); pureComponentPrototype.isPureReactComponent = true;
基本代碼和Component相似,也繼承自它的原型.但不繼承其自身的屬性方法.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/106499.html
摘要:調用棧是這樣的這里生成的我們將其命名為,它將作為參數傳入到。整個的調用棧是這樣的組件間的層級結構是這樣的到此為止,頂層對象已經構造完畢,下一步就是調用來自的方法,進行頁面的渲染了。通過表達的結構最終會轉化為一個純對象,用于下一步的渲染。 歡迎關注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言...
前言:使用react也有二年多了,一直停留在使用層次。雖然很多時候這樣是夠了。但是總覺得不深入理解其背后是的實現邏輯,很難體會框架的精髓。最近會寫一些相關的一些文章,來記錄學習的過程。 備注:react和react-dom源碼版本為16.8.6 本文適合使用過React進行開發,并有一定經驗的人閱讀。 好了閑話少說,我們一起來看源碼吧寫過react知道,我們使用react編寫代碼都離不開webpa...
摘要:本篇開始介紹自定義組件是如何渲染的。組件將自定義組件命名為,結構如下經過編譯后,生成如下代碼構建頂層包裝組件跟普通元素渲染一樣,第一步先會執行創建為的。調用順序已在代碼中注釋。先看圖,這部分內容將在下回分解 前言 React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務器渲染等,導致其代碼抽象化程度很高,嵌套層級非常深,閱讀其源碼是一個非...
摘要:既然看不懂,那就看看社區前輩們寫的一些源碼分析文章以及實現思路吧,又這么過了幾天,總算是摸清點思路,于是在參考了前輩們的基礎上,實現了一個簡易版的。總結以上就是實現一個的總體思路,下節我們重點放在不同的上。 寫在開頭 工作中使用react也很長一段時間了,雖然對它的用法,原理有了一定的了解,但是總感覺停留在表面。本著知其然知其所以然的態度,我試著去看了react源碼,幾天下來,發現并不...
摘要:我們先來看下這個函數的一些神奇用法對于上述代碼,也就是函數來說返回值是。不管你第二個參數的函數返回值是幾維嵌套數組,函數都能幫你攤平到一維數組,并且每次遍歷后返回的數組中的元素個數代表了同一個節點需要復制幾次。這是我的 React 源碼解讀課的第一篇文章,首先來說說為啥要寫這個系列文章: 現在工作中基本都用 React 了,由此想了解下內部原理 市面上 Vue 的源碼解讀數不勝數,但是反觀...
閱讀 1972·2021-11-25 09:43
閱讀 653·2021-10-11 10:58
閱讀 1730·2019-08-30 15:55
閱讀 1725·2019-08-30 13:13
閱讀 736·2019-08-29 17:01
閱讀 1840·2019-08-29 15:30
閱讀 789·2019-08-29 13:49
閱讀 2172·2019-08-29 12:13