每次都信誓旦旦的給本身立下要好好學習react源碼的flag,結果都是由於某個地方卡住了,或是其餘緣由沒看多少就放棄了。此次又給本身立個flag-堅持看完react
源碼。爲了敦促本身,特開設這樣一個專欄來記錄本身的學習歷程,這意味着這個專欄的文章質量並不高,你能夠拿來參考參考,切莫全信,我不想誤人子弟,後面要是學有所成再考慮產出些好點的文章。 要是發現文章中有什麼不當之處,歡迎批評交流。我看的源碼版本是16.8.2
。我是用在源碼加註釋的方法學習的,放在 github上。
爲了看react源碼,我查找了很多資料,這裏推薦兩個參考資料,我的以爲寫得不錯。javascript
15.6.2
的, 在react16
裏面一些方法找不到了。Component, PureComponent是咱們最經常使用的東西,咱們常常繼承他們來建立組件。所以,我選擇從這幾個最最經常使用的東西入手開始欣賞React源碼。他們都位於packages/react目錄下,入口在index.js,index.js裏邊導出的實際上是src下的React.js裏的東西,在React.js中能夠看到React暴露的API。在React.js中能夠找到上面說述的Component,PureComponent和ReactElement相關線索。html
Component和PureComponent都位於/packages/react/src/ReactBaseClasses.js。java
這兩個東西都是構造函數,或者稱爲類。react
Component的構造函數長成以下這樣:git
/** * 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. // 這個new的時候須要注意updater是哪裏來的, 這個updater與setState應該有很大關係 this.updater = updater || ReactNoopUpdateQueue; }
這並無什麼神奇的,他接收三個參數,掛到this上。具體是這三個參數是啥,我目前也是不清楚的,由於咱們平時使用都是extends他而並無new他,new的過程應該是框架去作的,這個獲得後面再作分析。後面分析時須要注意updater,感受這裏會是一個重點,他有一個默認值,ReactNoopUpdateQueue,去看了下他的代碼,他是一個對象,掛了一些方法,這裏也就不展開了,我也沒太細看。github
Component的原型上掛了一些方法和屬性,isReactComponent屬性,setState方法,forceUpdate方法,代碼以下:api
// 一般isXxx都是boolean類型的,這裏比較奇怪,後面須要關注下 Component.prototype.isReactComponent = {}; /** * ...這裏有不少說明,能夠直接去看 * * @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 */ // 原來咱們平時調用的setState就這麼幾行啊,可是看他是調用的updater的enqueueSetState, // 相關實現應該在那裏邊了, 能夠updater這個東西很厲害 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'); }; /** * ...這裏有不少說明,能夠直接去看 * * @param {?function} callback Called after update is complete. * @final * @protected */ // 不多用到這個方法啊, 但他和setState同樣都是Component原型上的方法 Component.prototype.forceUpdate = function(callback) { this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); };
其實Component的原型上掛載的東西也沒什麼神奇的,其中很是重要的是updater的enqueueSetState,enqueueForceUpdate方法,進一步說明了updater是後面分析的重點。框架
接下來的一段代碼是用來在開發模式下標記廢棄的api的,在開發模式下回給寫提示,代碼以下:函數
// 這裏是標識一些廢棄的api, 開發模式會報出來提醒開發這注意 if (__DEV__) { const deprecatedAPIs = { isMounted: [ 'isMounted', 'Instead, make sure to clean up subscriptions and pending requests in ' + 'componentWillUnmount to prevent memory leaks.', ], replaceState: [ 'replaceState', 'Refactor your code to use setState instead (see ' + 'https://github.com/facebook/react/issues/3236).', ], }; const defineDeprecationWarning = function(methodName, info) { Object.defineProperty(Component.prototype, methodName, { get: function() { lowPriorityWarning( false, '%s(...) is deprecated in plain JavaScript React classes. %s', info[0], info[1], ); return undefined; }, }); }; for (const fnName in deprecatedAPIs) { if (deprecatedAPIs.hasOwnProperty(fnName)) { defineDeprecationWarning(fnName, deprecatedAPIs[fnName]); } } }
__DEV__這個東西我沒找到是在哪裏掛到全局的(知道的同窗能夠留言指點下),可是看變量名能夠推測他是開發模式標識,這個提示咱們在作一些給別人用的東西時,接口協議約定十分重要,一旦約定就不能輕易變動,確實須要變動時須要通知調用方調整。回頭來,這裏標識廢棄了isMounted,replaceState兩個方法,其實他們被挪到了updater裏邊。oop
開始用React時老大Rewview個人代碼時常常寫評論,「你這個Component能夠改爲PureComponent」,當時一直不懂PureComponent與Component的區別(如今也沒全懂),只是聽人說PureComponent更新的時候是淺比較,而Component是深比較。今天看了這部分,其實也沒懂,不過感受後面再看看應該就懂了。要搞清這裏的PureComponet須要瞭解下js中繼承的實現,你們能夠參考《JavaScript高級程序設計》相關介紹,也能夠看看理解js繼承的6種方式, 筆者看到這個PureComponet也是先複習了下才看的。無論你看沒看, 代碼先貼出來:
// PureComponent function ComponentDummy() {} ComponentDummy.prototype = Component.prototype; // 發現PureComponnet的構造方法和Component是相同的 /** * 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;
我畫了個圖來理解這個繼承。
首先是建立了一個ComponentDummy構造函數,他的原型指到Component的原型;而後建立了一個PureComponent, 加上了和Component同樣的屬性(這裏爲啥不用call)。PureComponent的原型指向ComponentDummy的實例;修改PureComponent原型的constructor屬性使其正確指向PureComponent的構造函數,並掛一個isPureReactComponent的屬性。爲了減小向上去查找原型鏈次數,用了一個assign直接將Component原型的東西拷貝到PureComponent的原型上(這裏仍是考慮的比較精細的)。
首先這個實現沒有啥問題,可是我有個疑問,你們能夠留言指點下:
爲何要用繼承,注意到PureComponent的構造函數和Component是同樣的,而後還有一個拷貝Component的原型到PureComponent的原型的操做,那這裏有繼承的必要嗎?不都是重寫的嗎,感受畫蛇添足。
下一篇預告 ReactElement源碼解析