摘要:行最終返回了這個封裝好的構造函數。看到這里我們可以明白一點,組件實質上是一個構造函數,而我們自定義的方法,既存在了里,也按照的方式歸納到了里。到這里我們大概地知道了一個組件從創建構造函數到實例化的時候做了什么事情了。
對一個框架源碼的解讀,既有利于更深入地了解框架,使用上更得心應手,又可以學習到其中代碼組織的思路,吸收其精華簡潔的寫法以便于日常工作上使用。下面我就挑選近年大熱門react(15.3.1),從中剖析框架的設計思路,由淺入深地學習。
我們從這個文件開始看起,這是react的主入口(./lib/react.js)。
/** * Copyright 2013-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule React */ "use strict"; var _assign = require("object-assign"); var ReactChildren = require("./ReactChildren"); var ReactComponent = require("./ReactComponent"); var ReactPureComponent = require("./ReactPureComponent"); var ReactClass = require("./ReactClass"); var ReactDOMFactories = require("./ReactDOMFactories"); var ReactElement = require("./ReactElement"); var ReactPropTypes = require("./ReactPropTypes"); var ReactVersion = require("./ReactVersion"); var onlyChild = require("./onlyChild"); var warning = require("fbjs/lib/warning"); var createElement = ReactElement.createElement; var createFactory = ReactElement.createFactory; var cloneElement = ReactElement.cloneElement; if (process.env.NODE_ENV !== "production") { var ReactElementValidator = require("./ReactElementValidator"); createElement = ReactElementValidator.createElement; createFactory = ReactElementValidator.createFactory; cloneElement = ReactElementValidator.cloneElement; } var __spread = _assign; if (process.env.NODE_ENV !== "production") { var warned = false; __spread = function () { process.env.NODE_ENV !== "production" ? warning(warned, "React.__spread is deprecated and should not be used. Use " + "Object.assign directly or another helper function with similar " + "semantics. You may be seeing this warning due to your compiler. " + "See https://fb.me/react-spread-deprecation for more details.") : void 0; warned = true; return _assign.apply(null, arguments); }; } var React = { // Modern Children: { map: ReactChildren.map, forEach: ReactChildren.forEach, count: ReactChildren.count, toArray: ReactChildren.toArray, only: onlyChild }, Component: ReactComponent, PureComponent: ReactPureComponent, createElement: createElement, cloneElement: cloneElement, isValidElement: ReactElement.isValidElement, // Classic PropTypes: ReactPropTypes, createClass: ReactClass.createClass, createFactory: createFactory, createMixin: function (mixin) { // Currently a noop. Will be used to validate and trace mixins. return mixin; }, // This looks DOM specific but these are actually isomorphic helpers // since they are just generating DOM strings. DOM: ReactDOMFactories, version: ReactVersion, // Deprecated hook for JSX spread, don"t use this for anything. __spread: __spread }; module.exports = React;
我們直接跳過前面的環境判斷以及模塊引入,可以看到從50行起就是React的關鍵代碼。并且我們可以清晰的從上面看到React所提供的方法。這是離我們使用者最近的一層,看到信息量不多。我們就按照開發的思路,一步一步地深入源碼。
編寫一個組件,當然是從創建開始,我們使用的是 React.createClass,不難發現,React.createClass實際上引用的是ReactClass.createClass。當然我們也可以用ES6的寫法直接繼承至React.Component.這兩種寫法有什么差異存在,我們先把懸念放在后面。
先從createClass的源碼看起(./lib/ReactClass)。
var ReactClass = { /** * Creates a composite component class given a class specification. * See https://facebook.github.io/react/docs/top-level-api.html#react.createclass * * @param {object} spec Class specification (which must define `render`). * @return {function} Component constructor function. * @public */ createClass: function (spec) { var Constructor = function (props, context, updater) { // This constructor gets overridden by mocks. The argument is used // by mocks to assert on what gets mounted. if (process.env.NODE_ENV !== "production") { process.env.NODE_ENV !== "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; } // Wire up auto-binding if (this.__reactAutoBindPairs.length) { bindAutoBindMethods(this); } this.props = props; this.context = context; this.refs = emptyObject; this.updater = updater || ReactNoopUpdateQueue; this.state = null; // ReactClasses doesn"t have constructors. Instead, they use the // getInitialState and componentWillMount methods for initialization. var initialState = this.getInitialState ? this.getInitialState() : null; if (process.env.NODE_ENV !== "production") { // We allow auto-mocks to proceed as if they"re returning null. if (initialState === undefined && this.getInitialState._isMockFunction) { // This is probably bad practice. Consider warning here and // deprecating this convenience. initialState = null; } } !(typeof initialState === "object" && !Array.isArray(initialState)) ? process.env.NODE_ENV !== "production" ? invariant(false, "%s.getInitialState(): must return an object or null", Constructor.displayName || "ReactCompositeComponent") : _prodInvariant("82", Constructor.displayName || "ReactCompositeComponent") : void 0; this.state = initialState; }; Constructor.prototype = new ReactClassComponent(); Constructor.prototype.constructor = Constructor; Constructor.prototype.__reactAutoBindPairs = []; injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor)); mixSpecIntoComponent(Constructor, spec); // Initialize the defaultProps property after all mixins have been merged. if (Constructor.getDefaultProps) { Constructor.defaultProps = Constructor.getDefaultProps(); } if (process.env.NODE_ENV !== "production") { // This is a tag to indicate that the use of these method names is ok, // since it"s used with createClass. If it"s not, then it"s likely a // mistake so we"ll warn you to use the static property, property // initializer or constructor respectively. if (Constructor.getDefaultProps) { Constructor.getDefaultProps.isReactClassApproved = {}; } if (Constructor.prototype.getInitialState) { Constructor.prototype.getInitialState.isReactClassApproved = {}; } } !Constructor.prototype.render ? process.env.NODE_ENV !== "production" ? invariant(false, "createClass(...): Class specification must implement a `render` method.") : _prodInvariant("83") : void 0; if (process.env.NODE_ENV !== "production") { process.env.NODE_ENV !== "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; process.env.NODE_ENV !== "production" ? warning(!Constructor.prototype.componentWillRecieveProps, "%s has a method called " + "componentWillRecieveProps(). Did you mean componentWillReceiveProps()?", spec.displayName || "A component") : void 0; } // Reduce time spent doing lookups by setting these on the prototype. for (var methodName in ReactClassInterface) { if (!Constructor.prototype[methodName]) { Constructor.prototype[methodName] = null; } } return Constructor; }, injection: { injectMixin: function (mixin) { injectedMixins.push(mixin); } } };
644行起,createClass方法首先定義了一個Constructor構造函數,折疊內部,我們看看這個方法在返回一個構造函數前做了什么,
直接跳到681行,構造函數的prototype指向一個ReactClassComponent的實例。
Constructor.prototype = new ReactClassComponent();
往上翻我們可以發現,ReactClassComponent的prototype屬性,拷貝了ReactComponent.prototype 和 ReactClassMixin,因此我們的組件可以使用ReactComponent原型上的方法。
var ReactClassComponent = function () {}; _assign(ReactClassComponent.prototype, ReactComponent.prototype, ReactClassMixin);
683行到687行。
定義了 __reactAutoBindPairs 為一個空數組。
先將mixin里面的方法按照key,function內容的順序成對存入 __reactAutoBindPairs ,
接著就是spec對象里的方法用同樣的方式存入。
Constructor.prototype.__reactAutoBindPairs = []; injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor)); mixSpecIntoComponent(Constructor, spec);
690行我們可以看到Constructor.defaultProps 就是我們開發中 getDefaultProps()所返回的對象。
if (Constructor.getDefaultProps) { Constructor.defaultProps = Constructor.getDefaultProps(); }
694行 -- 712行 是在開發環境中對開發者的建議,以及規范使用的警示。
715行 -- 719行 可以知道我們創建一個組件需要定義的方法都在ReactClassInterface上有,當前未定義的方法設置為空,我們就可以通過打印組件的prototype屬性清楚地在日志上知道我們有哪些api是未定義的。通過設置未定義的屬性為空,可以減少程序查找的時間。
721行 最終返回了這個封裝好的構造函數。
for (var methodName in ReactClassInterface) { if (!Constructor.prototype[methodName]) { Constructor.prototype[methodName] = null; } } return Constructor;
看到這里我們可以明白一點,組件實質上是一個構造函數,而我們自定義的方法,既存在了prototype里,也按照[key,content,key,content...]的方式歸納到了Constructor.prototype.__reactAutoBindPairs 里。這是為了組件實例化時可以將這些方法直接遍歷綁定在實例上,并且避免了React官方指定的方法也被綁定在實例上。
接下來我們展開645行的Constructor,可以看到實例化的時候主要做了兩件事。
654行
第一件事就是將上文提到的存在Constructor.prototype.__reactAutoBindPairs 的內容成對取出,綁定在實例上。
if (this.__reactAutoBindPairs.length) { bindAutoBindMethods(this); }
668行 ——679行
第二件事就是判斷組件是否有定義getInitialState,如果有,則將state設置為該方法返回的值,如果沒有設置state為null。
var initialState = this.getInitialState ? this.getInitialState() : null; if (process.env.NODE_ENV !== "production") { // We allow auto-mocks to proceed as if they"re returning null. if (initialState === undefined && this.getInitialState._isMockFunction) { // This is probably bad practice. Consider warning here and // deprecating this convenience. initialState = null; } } !(typeof initialState === "object" && !Array.isArray(initialState)) ? process.env.NODE_ENV !== "production" ? invariant(false, "%s.getInitialState(): must return an object or null", Constructor.displayName || "ReactCompositeComponent") : _prodInvariant("82", Constructor.displayName || "ReactCompositeComponent") : void 0; this.state = initialState;
到這里我們大概地知道了一個組件從創建構造函數到實例化的時候做了什么事情了。后續我們繼續解讀更底層的ReactComponent。
希望能對大家有幫助。
如果有錯誤的地方,懇請各位大神指正。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/81184.html
摘要:前言的主要思想是通過構建可復用組件來構建頁面所謂組件其實就是有限狀態機通過狀態渲染對應的界面且每個組件都有自己的生命周期它規定了組件的狀態和方法需要在哪個階段改變和執行子組件子組件子組件子組件初探生命周期當首次掛載組件時按順序執行當卸載組件 前言 React的主要思想是通過構建可復用組件來構建頁面.所謂組件,其實就是有限狀態機(FSM),通過狀態渲染對應的界面,且每個組件都有自己的生命...
摘要:下面,我將從上到下挑選出能給自己一些啟發的源碼做注解。省略了很多代碼通過這段精簡的,我們看到,,返回的是一個構造函數,該函數原型繼承于,并將參數中定義的屬性合并到其中。 ReactClass.js 源文件: https://github.com/facebook/react/blob/master/src/isomorphic/classic/class/ReactClass.js 2...
摘要:目錄無繼承簡單的字段聲明無繼承簡單的方法聲明簡單繼承一層繼承字段覆蓋無繼承靜態函數無繼承靜態變量神秘的類無繼承簡單的字段聲明先來看個最簡單的例子,我們僅僅使用了關鍵字并定義了一個變量最后編譯出來的代碼如下。無繼承靜態變量還有個小例子。 在[上一篇文章][]中,我們提到 ES6 的 class 語法糖是個近乎完美的方案,并且講解了實現繼承的許多內部機制,如 prototype/__pro...
摘要:學習筆記之解讀前端技術不多說,大腿很粗什么是是的核心組成部分,它使用標記的方式去直接聲明界面,界面組件之間可以互相嵌套。它的目的是通過各種編譯器將這些標記編譯成標準的語言。的標簽與函數名都是使用的駝峰命名。目前,一個的,只能返回一個節點。 React.js學習筆記之JSX解讀 @(前端技術) Why React? 不多說,Facebook大腿很粗 什么是JSX JSX是React的核心...
摘要:的創建組件,其實根源還是調用了編譯之后一般寫法建議用來進行源碼的跟蹤鏈接從源碼角度來看創建一個組件的過程中發生了什么。 https://github.com/jimwmg/Rea... 1 React.createClass( ) var HelloWorld = React.createClass({ render : function(){ return ...
閱讀 4015·2023-04-26 02:13
閱讀 2260·2021-11-08 13:13
閱讀 2748·2021-10-11 10:59
閱讀 1745·2021-09-03 00:23
閱讀 1314·2019-08-30 15:53
閱讀 2293·2019-08-28 18:22
閱讀 3061·2019-08-26 10:45
閱讀 744·2019-08-23 17:58