以前一直在使用react作開發,可是對其內部的工做機制卻一點兒都不瞭解,說白了就是一直在套api,毫無成就感。趁最近比較閒,對源碼作了一番研究,並經過博客的方式作一些記錄。html
經過編寫自定義組件來實現代碼複用是react一個很亮眼的創新點,咱們知道react建立組件通常使用兩種方式:react
雖而後者正在逐漸取代前者,可是去研究一下前者也是頗有必要的。咱們先來看一看 createClass 方法,而後再去分析一下es6的寫法,經過對比你將發現 createClass 被取代也是有理可循的。咱們在源碼中找到createClass方法,如圖,在貼出的代碼中我將做者原來的註釋都刪除了,並在必要的地方加上了本身的理解。webpack
1 var ReactClass = { 2 3 createClass: function (spec) { 4 5 //warning:createClass API將會在16版本被移除,如今一般是經過es6的語法建立組件,後面會進行說明 6 if ("development" !== 'production') { 7 "development" !== 'production' ? warning(didWarnDeprecated, '%s: React.createClass is deprecated and will be removed in version 16. ' + 'Use plain JavaScript classes instead. If you\'re not yet ready to ' + 'migrate, create-react-class is available on npm as a ' + 'drop-in replacement.', spec && spec.displayName || 'A Component') : void 0; 8 didWarnDeprecated = true; 9 } 10 11 //identity返回參數中的匿名函數 12 var Constructor = identity(function (props, context, updater) { 13 14 //warning: 必須經過new方法建立Constructor實例來使用組件 15 if ("development" !== 'production') { 16 "development" !== 'production' ? warning(this instanceof Constructor, 'Something is calling a React component directly. Use a factory or ' + 'JSX instead. See: https://fb.me/react-legacyfactory') : void 0; 17 } 18 19 // 處理自動綁定的方法 20 if (this.__reactAutoBindPairs.length) { 21 bindAutoBindMethods(this); 22 } 23 24 this.props = props; 25 this.context = context; 26 this.refs = emptyObject; 27 this.updater = updater || ReactNoopUpdateQueue; 28 this.state = null; 29 30 //調用 getInitialState 初始化state 31 var initialState = this.getInitialState ? this.getInitialState() : null; 32 if ("development" !== 'production') { 33 if (initialState === undefined && this.getInitialState._isMockFunction) { 34 initialState = null; 35 } 36 } 37 //當 initialState 返回的不是object或null類型拋出錯誤 38 !(typeof initialState === 'object' && !Array.isArray(initialState)) ? "development" !== 'production' ? invariant(false, '%s.getInitialState(): must return an object or null', Constructor.displayName || 'ReactCompositeComponent') : _prodInvariant('82', Constructor.displayName || 'ReactCompositeComponent') : void 0; 39 40 this.state = initialState; 41 }); 42 //使Constructor繼承ReactClassComponent 43 Constructor.prototype = new ReactClassComponent(); 44 Constructor.prototype.constructor = Constructor; 45 Constructor.prototype.__reactAutoBindPairs = []; 46 47 //將經過 ReactClass.injection.injectMixin(mixin) 方法傳入的mixin注入到Constructor中去 48 injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor)); 49 50 // 將 createClass 參數中定義的屬性或方法混入到 Constructor.prototype 或 Constructor 中,其中分了不少狀況,後面詳細說明 51 mixSpecIntoComponent(Constructor, spec); 52 53 // 調用 getDefaultProps 初始化 defaultProps,掛載組件時會做爲參數傳給new Constructor(),注意 getDefaultProps 不在prototype上 54 if (Constructor.getDefaultProps) { 55 Constructor.defaultProps = Constructor.getDefaultProps(); 56 } 57 58 if ("development" !== 'production') { 59 // getDefaultProps 只能在createClass中使用,經過 isReactClassApproved 防止在外部調用 60 if (Constructor.getDefaultProps) { 61 Constructor.getDefaultProps.isReactClassApproved = {}; 62 } 63 // 同上 64 if (Constructor.prototype.getInitialState) { 65 Constructor.prototype.getInitialState.isReactClassApproved = {}; 66 } 67 } 68 // 必須定義render方法,不然拋出錯誤 69 !Constructor.prototype.render ? "development" !== 'production' ? invariant(false, 'createClass(...): Class specification must implement a `render` method.') : _prodInvariant('83') : void 0; 70 // 一些拼寫錯誤的warning 71 if ("development" !== 'production') { 72 "development" !== 'production' ? warning(!Constructor.prototype.componentShouldUpdate, '%s has a method called ' + 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + 'The name is phrased as a question because the function is ' + 'expected to return a value.', spec.displayName || 'A component') : void 0; 73 "development" !== 'production' ? warning(!Constructor.prototype.componentWillRecieveProps, '%s has a method called ' + 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?', spec.displayName || 'A component') : void 0; 74 } 75 76 // 這是一個優化,後面詳細說明 77 for (var methodName in ReactClassInterface) { 78 if (!Constructor.prototype[methodName]) { 79 Constructor.prototype[methodName] = null; 80 } 81 } 82 83 return Constructor; 84 }, 85 86 injection: { 87 injectMixin: function (mixin) { 88 injectedMixins.push(mixin); 89 } 90 } 91 92 };
首先能夠看到,createClass 實際上返回的是一個 Constructor 構造函數,該構造函數擁有5個實例屬性,分別是:props,context,refs,updater和state。props 即爲一般咱們用於組件之間通訊的 props,存儲組件的一些相關屬性。它實際上由3個部分組成:git
調用 createElement 時傳入的config,children 和 createClass 中調用 getDefaultProps 方法得到的 defaultProps。舉個例子,以下列代碼: es6
var Root = React.createClass({ getDefaultProps: function() { return { name: 'Ray' } }, render: function() { let me = this; console.log(me.props); return ( <div> <h1>Hello, {me.props.name}, {me.props.age} years old</h1> {this.props.children} </div> ) } }) var ele = React.createElement(Root, {age: '18'}, "i'm a child"); ReactDOM.render( ele, document.getElementById('dom') );
打印出props,以下圖:github
能夠看到 props 屬性包含了在 createElement 中傳入的config和children參數,還有在 getDefaultProps 中定義的默認值,這也就是爲何咱們能夠在組件用 {this.props.children} 這種方式獲取到children。web
關於 createElement 的具體實現會在以後相關文章中分析。npm
第二個實例屬性 context 即保存組件掛載或更新時的上下文環境,react會爲你處理好一切,這裏就不展開了。api
而後是refs,保存着對組件render方法內的dom節點的引用。若是是自定義的元素節點,即咱們建立的組件,則會保存着一個咱們在掛載組件時用到的 ReactCompositeComponent 的實例。若是是基本元素類型,如div,p等,則直接返回其真實dom。注意,該屬性不能再render中使用,由於此時組件還未掛載。數組
updater屬性保存着組件的更新隊列,在組件更新時會用到。
state屬性即保存組件的狀態。
在定義了Constructor構造函數後(注意:此時還未執行該函數,僅僅是定義,生成實例是在掛載組件的時候進行的),見41,42行,運用了簡單的原型鏈繼承,是 Costructor 對象繼承了 ReactClassComponent,咱們來看看ReactClassComponent具體有哪些屬性和方法。
1 var ReactClassComponent = function () {}; 2 _assign(ReactClassComponent.prototype, ReactComponent.prototype, ReactClassMixin);
這段代碼將 ReactComponent.prototype 和 ReactClassMixin 混入到ReactClassComponent.prototype上,再看看這二者代碼。
function ReactComponent(props, context, updater) { this.props = props; this.context = context; this.refs = emptyObject; // We initialize the default updater but the real one gets injected by the // renderer. this.updater = updater || ReactNoopUpdateQueue; } ReactComponent.prototype.isReactComponent = {}; ReactComponent.prototype.setState = function (partialState, callback) { !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? "development" !== 'production' ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : _prodInvariant('85') : void 0; this.updater.enqueueSetState(this, partialState); if (callback) { this.updater.enqueueCallback(this, callback, 'setState'); } }; ReactComponent.prototype.forceUpdate = function (callback) { this.updater.enqueueForceUpdate(this); if (callback) { this.updater.enqueueCallback(this, callback, 'forceUpdate'); } }; var ReactClassMixin = { replaceState: function (newState, callback) { this.updater.enqueueReplaceState(this, newState); if (callback) { this.updater.enqueueCallback(this, callback, 'replaceState'); } }, isMounted: function () { return this.updater.isMounted(this); } };
所以經過 Constructor.prototype 一共能夠訪問到5個方法,分別爲:isReactComponent,setState,forceUpdate,replaceState和isMounted。
而後在48行有 injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor)) 這樣一段代碼,在第86行能夠看到,ReactClass 還有一個方法 injection,這個方法會把要混入的mixin添加到 injectedMixins 數組中。所以這裏的 forEach 也就是要把 injectedMixins中全部的mixin混入到Constructor中去,這和 createClass的功能有些重複,而且 injection 方法並無暴露到 React 中去,所以我也不太明白這個方法存在的意義。
接下來在51行執行 mixSpecIntoComponent(Constructor, spec) ,mixSpecIntoComponent 就是把咱們傳入的spec中的方法或屬性混入到 Constructor,咱們來看看這個方法。
1 function mixSpecIntoComponent(Constructor, spec) { 2 // 當傳入的spec爲null或者undefined時顯示warning信息 3 if (!spec) { 4 if ("development" !== 'production') { 5 var typeofSpec = typeof spec; 6 var isMixinValid = typeofSpec === 'object' && spec !== null; 7 8 "development" !== 'production' ? warning(isMixinValid, '%s: You\'re attempting to include a mixin that is either null ' + 'or not an object. Check the mixins included by the component, ' + 'as well as any mixins they include themselves. ' + 'Expected object but got %s.', Constructor.displayName || 'ReactClass', spec === null ? null : typeofSpec) : void 0; 9 } 10 11 return; 12 } 13 14 // 當傳入的spec爲function類型或者是ReactElement對象,拋出異常 15 !(typeof spec !== 'function') ? "development" !== 'production' ? invariant(false, 'ReactClass: You\'re attempting to use a component class or function as a mixin. Instead, just use a regular object.') : _prodInvariant('75') : void 0; 16 !!ReactElement.isValidElement(spec) ? "development" !== 'production' ? invariant(false, 'ReactClass: You\'re attempting to use a component as a mixin. Instead, just use a regular object.') : _prodInvariant('76') : void 0; 17 18 var proto = Constructor.prototype; 19 // __reactAutoBindPairs爲一個數組的引用,見createClass方法,對autoBindPairs的修改會影響到__reactAutoBindPairs所引用的數組 20 var autoBindPairs = proto.__reactAutoBindPairs; 21 22 // 先處理spec中的mixins字段,MIXINS_KEY即爲'mixins' 23 if (spec.hasOwnProperty(MIXINS_KEY)) { 24 RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins); 25 } 26 27 for (var name in spec) { 28 if (!spec.hasOwnProperty(name)) { 29 continue; 30 } 31 32 if (name === MIXINS_KEY) { 33 // mixins字段前面已經處理過了 34 continue; 35 } 36 37 var property = spec[name]; 38 var isAlreadyDefined = proto.hasOwnProperty(name); 39 validateMethodOverride(isAlreadyDefined, name); 40 41 //如果RESERVED_SPEC_KEYS中定義過的屬性,則按照RESERVED_SPEC_KEYS中定義方法執行相應操做 42 if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) { 43 RESERVED_SPEC_KEYS[name](Constructor, property); 44 } else { 45 // 知足如下條件的方法不該該被auto bind: 46 // 1. 是ReactClassInterface中定義的方法. 47 // 2. Constructor.prototype上已存在的方法. 48 var isReactClassMethod = ReactClassInterface.hasOwnProperty(name); 49 var isFunction = typeof property === 'function'; 50 var shouldAutoBind = isFunction && !isReactClassMethod && !isAlreadyDefined && spec.autobind !== false; 51 52 if (shouldAutoBind) { 53 // 將應該自動綁定的方法添加到autoBindPairs所引用的數組中 54 autoBindPairs.push(name, property); 55 proto[name] = property; 56 } else { 57 if (isAlreadyDefined) { 58 var specPolicy = ReactClassInterface[name]; 59 60 // 這部分操做已經在 validateMethodOverride 執行過了 61 !(isReactClassMethod && (specPolicy === 'DEFINE_MANY_MERGED' || specPolicy === 'DEFINE_MANY')) ? "development" !== 'production' ? invariant(false, 'ReactClass: Unexpected spec policy %s for key %s when mixing in component specs.', specPolicy, name) : _prodInvariant('77', specPolicy, name) : void 0; 62 63 // 對於擁有 DEFINE_MANY_MERGED 或者 DEFINE_MANY標識的方法,當定義屢次時,分別採起不一樣的處理 64 if (specPolicy === 'DEFINE_MANY_MERGED') { 65 proto[name] = createMergedResultFunction(proto[name], property); 66 } else if (specPolicy === 'DEFINE_MANY') { 67 proto[name] = createChainedFunction(proto[name], property); 68 } 69 } else { 70 proto[name] = property; 71 if ("development" !== 'production') { 72 // 給function類型的屬性添加displayName屬性,至關添加了個標識符,給使用分析工具提供了幫助 73 if (typeof property === 'function' && spec.displayName) { 74 proto[name].displayName = spec.displayName + '_' + name; 75 } 76 } 77 } 78 } 79 } 80 } 81 }
直接從20行開始說,這裏建立了一個 autoBindPairs 變量,將 proto.__reactAutoBindPairs 賦給了它。而 proto.__reactAutoBindPairs 是一個對數組的引用,所以在該段代碼54行執行 autoBindPairs.push(name, property) 時,實際上也影響到了 proto.__reactAutoBindPairs,由於他倆指向同一個數組,對其中任何一個的操做將會影響到另外一個。
接下來在22行處理spec的注入時,首先會去處理mixins字段,不管它是不是第一個定義的,此時會去調用 RESERVED_SPEC_KEYS 中對應的方法。RESERVED_SPEC_KEYS 顧名思義,就是列出spec的一些保留的關鍵字,作特殊的處理。例如 getDefaultProps,propTypes等,這個對象中定義的屬性是會被添加到Constructor上,而不是Constructor.prototype上,即做爲Constructor的靜態屬性存在。這裏在處理mixins時,實際上就是遍歷mixins數組,而後對每一項執行 mixSpecIntoComponent 方法,這是一個遞歸的過程。這裏就有一個問題,假如mixins和spec包含同名屬性,該如何處理?不着急,繼續往下看。
處理了mixins以後,就會去遍歷spec對象,去處理其它的字段。在處理以前,首先會去調用 validateMethodOverride 方法,這個方法就是用來處理當混入了兩個同名屬性的狀況,咱們來看一下這段代碼。
1 function validateMethodOverride(isAlreadyDefined, name) { 2 var specPolicy = ReactClassInterface.hasOwnProperty(name) ? ReactClassInterface[name] : null; 3 4 // Disallow overriding of base class methods unless explicitly allowed. 5 if (ReactClassMixin.hasOwnProperty(name)) { 6 !(specPolicy === 'OVERRIDE_BASE') ? "development" !== 'production' ? invariant(false, 'ReactClassInterface: You are attempting to override `%s` from your class specification. Ensure that your method names do not overlap with React methods.', name) : _prodInvariant('73', name) : void 0; 7 } 8 9 // Disallow defining methods more than once unless explicitly allowed. 10 if (isAlreadyDefined) { 11 !(specPolicy === 'DEFINE_MANY' || specPolicy === 'DEFINE_MANY_MERGED') ? "development" !== 'production' ? invariant(false, 'ReactClassInterface: You are attempting to define `%s` on your component more than once. This conflict may be due to a mixin.', name) : _prodInvariant('74', name) : void 0; 12 } 13 }
ReactClassInterface能夠理解爲一個對象,對象中包含的key爲屬性或者方法名,value爲對該屬性或方法的描述,代碼以下(爲了簡潔,刪除了註釋)。
1 var ReactClassInterface = { 2 3 mixins: 'DEFINE_MANY', 4 5 statics: 'DEFINE_MANY', 6 7 propTypes: 'DEFINE_MANY', 8 9 contextTypes: 'DEFINE_MANY', 10 11 childContextTypes: 'DEFINE_MANY', 12 13 getDefaultProps: 'DEFINE_MANY_MERGED', 14 15 getInitialState: 'DEFINE_MANY_MERGED', 16 17 getChildContext: 'DEFINE_MANY_MERGED', 18 19 render: 'DEFINE_ONCE', 20 21 componentWillMount: 'DEFINE_MANY', 22 23 componentDidMount: 'DEFINE_MANY', 24 25 componentWillReceiveProps: 'DEFINE_MANY', 26 27 shouldComponentUpdate: 'DEFINE_ONCE', 28 29 componentWillUpdate: 'DEFINE_MANY', 30 31 componentDidUpdate: 'DEFINE_MANY', 32 33 componentWillUnmount: 'DEFINE_MANY', 34 35 updateComponent: 'OVERRIDE_BASE' 36 37 };
例如componentWillMount: 'DEFINE_MANY', 就是規定 componentWillMount 能夠定義屢次。
回到 validateMethodOverride 方法,首先 ReactClassMixin 對象前面說過,包含replaceState和isMounted方法,而描述爲 OVERRIDE_BASE 的只有 updateComponent 方法,因此第一個if語句規定不能重寫 ReactClassMixin 中的方法,不然拋出異常。isAlreadyDefined 爲在Constructor.prototype中已存在的方法,若是一個方法已存在且不爲 DEFINE_MANY 或者 DEFINE_MANY_MERGED,則拋出異常。若是是這兩個其中一個的話,來看 mixSpecIntoComponent 的64-68行, 若是爲 DEFINE_MANY_MERGED,會去調用 createMergedResultFunction 方法;若是爲 DEFINE_MANY 會去調用 createChainedFunction 方法。咱們來看看這兩個函數的代碼。
1 function mergeIntoWithNoDuplicateKeys(one, two) { 2 !(one && two && typeof one === 'object' && typeof two === 'object') ? "development" !== 'production' ? invariant(false, 'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.') : _prodInvariant('80') : void 0; 3 4 for (var key in two) { 5 if (two.hasOwnProperty(key)) { 6 !(one[key] === undefined) ? "development" !== 'production' ? invariant(false, 'mergeIntoWithNoDuplicateKeys(): Tried to merge two objects with the same key: `%s`. This conflict may be due to a mixin; in particular, this may be caused by two getInitialState() or getDefaultProps() methods returning objects with clashing keys.', key) : _prodInvariant('81', key) : void 0; 7 one[key] = two[key]; 8 } 9 } 10 return one; 11 } 12 13 function createMergedResultFunction(one, two) { 14 return function mergedResult() { 15 var a = one.apply(this, arguments); 16 var b = two.apply(this, arguments); 17 if (a == null) { 18 return b; 19 } else if (b == null) { 20 return a; 21 } 22 var c = {}; 23 mergeIntoWithNoDuplicateKeys(c, a); 24 mergeIntoWithNoDuplicateKeys(c, b); 25 return c; 26 }; 27 } 28 29 function createChainedFunction(one, two) { 30 return function chainedFunction() { 31 one.apply(this, arguments); 32 two.apply(this, arguments); 33 }; 34 }
簡單來講,這兩個方法都返回了一個函數,返回的函數主要的做用是按順序調用兩個同名方法。不過在處理 DEFINE_MANY_MERGED 的時候,須要將執行後返回的對象合併,此時對象中若存在同名的屬性,則會拋出異常。經過以上代碼的第6行,咱們能夠看到這種狀況一般是在mixins 和 spec中混入了 getInitialState 或者 getDefaultProps,而且返回的對象中包含同名屬性形成的。所以能夠得出一個結論,當咱們在mixins或者spec中混入了多個 componentDidMount 方法,結果將會按順序執行;而混入多個自定義的方法,由於缺乏了 DEFINE_MANY 的描述,將會拋出異常。
最後幾行是給某些屬性或方法添加displayName字段便於區分,通過那麼多if else條件語句篩選,這裏所說的某些屬性或方法基本就是指在 ReactClassInterface 中定義的但不存在於 RESERVED_SPEC_KEYS 中的屬性或方法。關於displayName,若是在構建環境時用了webpack而且引入babel,那麼其中的 babel-preset-react 會去引入一個叫 babel-plugin-transform-react-display-name 的模塊,這個模塊會自動的爲你的組件混入一個displayName屬性,值爲你定義的變量的值。如 var foo = React.createClass({}),那麼添加的displayName就是foo。
至此,mixSpecIntoComponent 的過程大體的都描述完了,咱們能夠來理一理這裏的處理順序:
接着回到 createClass 方法,繼續往下看。
執行完 mixSpecIntoComponent 以後,54行執行 getDefaultProps 並把結果賦給了 Constructor 的一個defaultProps 靜態屬性。由此可知,getDefaultProps 會先於 getInitialState 執行,後者是在31行建立Constructor實例時才被調用。所以,不要在 getDefaultProps 中使用 this.state 屬性,由於此時 state 還未被賦值。
最後咱們來看77行,給 Constructor.prototype 的某些屬性賦了一個null,這些屬性是在 ReactClassInterface 上定義但未在 Constructor.prototype 上定義的。這麼作的目的根據我我的的理解,是由於在首次掛在組件的時候,會先去判斷 Constructor.prototype.componentWillMount 是否存在,若存在則調用。若是該屬性在 Constructor.prototype 上未定義,那麼咱們必須遍歷完整個prototype對象才能夠得出結論;然而在prototype上賦了一個null,能夠有效地防止沒必要要的遍歷。這一樣適用於組件更新時執行componentWillUpdate等方法,且效果更爲明顯,由於更新組件的操做在React中是至關頻繁的。
createClass的過程到此就執行完了,咱們再來回頭看看Constructor構造函數,它在建立實例對象時還會去作一件事,bindAutoBindMethods,見21行。這個方法主要是使用bind方法綁定做用域到Constructor實例上的,所以在使用createClass時沒必要擔憂方法做用域的問題,而使用es6建立組件時,必須本身去bind做用域。那麼 this.__reactAutoBindPairs 裏面的值是哪裏來的呢?不錯,就是在執行 mixSpecIntoComponent 這個方法時,經過對 autoBindPairs 變量進行操做,從而影響了 __reactAutoBindPairs。
說了一堆,其實 createClass 主要就作了這麼幾件事。
以上3點經過 class xxx extends React.Component 寫法都能辦到。React.Component 即爲 ReactComponent 對象,上面已經放過代碼了,它正好是含有props,context,refs,updater這4個屬性的構造函數,而且擁有 setState 和 forceUpdate 方法供咱們調用,所以咱們在建立組件時(stateless component除外),只需在constructor添加 this.state 用來初始化state屬性便可保持一致。第2點須要注意,es6的寫法並無爲你綁定做用域,須要本身手動在constructor中綁定,或者使用箭頭函數的特性。第3點,在class中定義的方法都會被添加到prototype上,也能夠知足條件。至於第4點,若須要定義defalutProps,只需手動添加,如:XXX.defalutProps 定義便可。惟一缺乏的就是對mixins的支持。其實mixins已經在官方文檔上被定義爲一個大坑,主要在於mixins 與 mixins,mixins與組件之間存在着千絲萬縷的依賴關係,代碼量一增長,就變得十分難維護。還有一點,正如上面分析,mixins會形成命名的衝突,當咱們定義兩個同名的自定義方法時會拋異常。官方已經給出了一個稱之爲‘高階組件’的解決方案來替代mixins了。具體的文檔請參見https://facebook.github.io/react/blog/2016/07/13/mixins-considered-harmful.html。既然mixins不建議再使用了,那麼 createClass 中 mixSpecIntoComponent 這個方法所作的不少事情都顯得多餘了,所以 es6 extends的寫法以其代碼的簡潔性取代createClass應該是理所固然的事。
第一次寫博客,不知道是否已經把該講的東西都講清楚了,但願看過的小夥伴都能給點意見,或者是博主有什麼理解誤區的,都歡迎指出。接下來還會陸續對ReactElement,組件的mount和update等進行詳細的說明,感謝各位賞臉閱讀。